/*
  Bano Qabil — operations console.

  Design thesis: 35,000 people have asked for a seat in a classroom, and there are only so many
  seats. That gap is the fact the whole institute runs on, so the interface is built as an
  instrument panel for it rather than as a filing cabinet.

    · Dark by default. This is a screen staff keep open all day beside a spreadsheet, and a dark
      plane lets the data be the only thing emitting light. Light is a full alternate skin, not an
      afterthought — the same panel under daylight, chosen explicitly and remembered per browser.
      The operating system's preference is deliberately not followed: staff should get the same
      console regardless of how their laptop is configured.

    · Two accents that mean something. Cyan is demand — applications, people, everything asked
      for. Violet is capacity — seats, rooms, everything the institute can actually give. Wherever
      the two meet on a chart, the distance between them is the story.

    · Micro-labels are set in mono, uppercase, wide-tracked. Every label on this screen names a
      measured quantity, and that is how an instrument labels a dial. Identifiers (CNIC, phone,
      registration numbers) stay mono for the functional reason: 13-digit numbers get compared
      column-to-column all day.

    · Space Grotesk carries titles and headline figures. Its numerals have real character at the
      size the numbers that matter are actually set at.

    · Glow appears in exactly three places — the active nav marker, the demand line on the trend
      chart, and a bar that has run past its capacity. It is a signal, not a finish. Everything
      else is flat, and depth comes from a surface ladder plus hairlines.

    · Colour carries state or series identity, never both. The eight series slots are assigned in
      fixed order and were validated as a set for contrast and colour-vision separation against
      each theme's own surface; status hues are a separate reserved set, and every one of them
      also ships a written label, so state is never conveyed by colour alone.
*/

/* ============================================================================
   TOKENS — dark is the default plane
   ============================================================================ */

:root {
  color-scheme: dark;

  /* --- Planes. A ladder from the page behind to the panel in front. --- */
  --canvas:      #090b11;
  --surface:     #12141c;
  --surface-2:   #171a24;
  --surface-3:   #1f2430;
  --rail:        #0c0e15;

  --line:        #222736;
  --line-2:      #333a4c;

  --ink:         #e8ecf7;
  --ink-2:       #97a1b9;
  --ink-3:       #6a7389;

  /* --- Cyan: demand. The accent that carries interaction as well as data. --- */
  --accent:      #1f9cae;
  --accent-hi:   #45d3e6;
  --accent-wash: #0e2831;
  --accent-edge: #1b4c58;
  --glow:        rgb(69 211 230 / 22%);

  /* --- Violet: capacity. Never used for interaction, so the pairing stays readable. --- */
  --violet:      #9085e9;
  --violet-wash: #191830;
  --violet-edge: #343064;

  /*
    Status. A reserved set, kept distinct from the series slots so a status colour can never be
    read as "series 9". Each is always accompanied by a written label.
  */
  --ok:          #0ca30c;
  --ok-wash:     #0c2110;
  --ok-edge:     #1a4a1f;

  --review:      #fab219;
  --review-wash: #291f07;
  --review-edge: #55430f;

  --danger:      #e05a5a;
  --danger-wash: #2b1214;
  --danger-edge: #58252a;

  --info:        #5b8fd0;
  --info-wash:   #111d2d;
  --info-edge:   #243d5b;

  /* Single-hue ramp for magnitude. On a dark plane the lighter step reads as "more". */
  --ramp-1: #123840;
  --ramp-2: #17545f;
  --ramp-3: #1a7181;
  --ramp-4: #1f9cae;
  --ramp-5: #35bed1;
  --ramp-6: #6ad9e8;

  --grid: #1e2331;
  --axis: #2e3547;

  /* 4px grid. Every gap, pad and offset is a multiple, which is what keeps a dense screen calm. */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s8: 32px; --s12: 48px;

  --radius:    12px;
  --radius-sm: 8px;
  --radius-xs: 5px;

  /*
    On a dark plane a drop shadow is invisible, so depth is the surface ladder plus hairline
    borders. Shadows are kept only for things that genuinely float above the page — modals, the
    command palette, chart tooltips.
  */
  --shadow-lg:  0 28px 64px -16px rgb(0 0 0 / 72%);
  --shadow-pop: 0 12px 32px -8px rgb(0 0 0 / 66%);
  /* Dark keeps its panels flat — the surface ladder is the depth. Light redefines this. */
  --shadow-card: none;

  /*
    The smallest thing a finger can reliably hit. Every control that appears on a phone is sized
    against it; on a desk the console stays as dense as it is, because a mouse does not need this.
  */
  --tap: 44px;

  /*
    Row density. Three named modes rather than one height, because the right density depends on
    the task: scanning 35,000 records wants compact, reading one record wants comfortable.
  */
  --row-pad-y: 11px;
  --row-font:  13px;

  --display: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
  --sans:    "IBM Plex Sans", ui-sans-serif, system-ui, "Segoe UI", -apple-system, sans-serif;
  --mono:    "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  --rail-w:   232px;
  --topbar-h: 56px;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  /*
    The measure — how wide the working column is allowed to get, and the one number the page and
    the topbar both hang off so they stay in the same column at every width.

    It is capped rather than left to fill because a table row three thousand pixels wide is a row
    nobody can follow from the name on the left to the status on the right; the eye loses the line.
    The cap is lifted once, on screens large enough that the old one was leaving a third of the
    display empty — see the note at .page.
  */
  --measure: 1560px;

  /*
    Categorical slots, assigned in fixed order and never cycled: a ninth series takes the neutral
    slot and leans on its label rather than an invented hue. Validated as a set against this
    theme's own surface — lightness band, chroma floor, adjacent-pair colour-vision separation,
    normal-vision separation and contrast all pass. The dark column is not an automatic flip of
    the light one; each is stepped for the plane it is drawn on.
  */
  --series-1: #1f9cae;  /* cyan    — demand  */
  --series-2: #9085e9;  /* violet  — capacity */
  --series-3: #d95926;  /* orange  */
  --series-4: #199e70;  /* aqua    */
  --series-5: #c98500;  /* yellow  */
  --series-6: #d55181;  /* magenta */
  --series-7: #3987e5;  /* blue    */
  --series-8: #008300;  /* green   */
  --series-other: #5c6478;
}

:root[data-theme="light"] {
  color-scheme: light;

  --canvas:      #eceff5;
  --surface:     #ffffff;
  --surface-2:   #f4f6fa;
  --surface-3:   #e7ebf3;
  --rail:        #f7f9fc;

  --line:        #dfe4ee;
  --line-2:      #c2cad9;

  --ink:         #0f1420;
  --ink-2:       #515b70;
  --ink-3:       #7e889d;

  --accent:      #0d8fa3;
  --accent-hi:   #0a7183;
  --accent-wash: #e1f3f7;
  --accent-edge: #a5dbe4;
  --glow:        rgb(13 143 163 / 16%);

  --violet:      #4a3aa7;
  --violet-wash: #ecebf8;
  --violet-edge: #c3bdea;

  --ok:          #0b8b0b;
  --ok-wash:     #e4f5e4;
  --ok-edge:     #b3e0b3;

  --review:      #a8760a;
  --review-wash: #fdf2da;
  --review-edge: #edd79b;

  --danger:      #c8323c;
  --danger-wash: #fdeaec;
  --danger-edge: #f2c1c6;

  --info:        #2a6bb0;
  --info-wash:   #e8f0f9;
  --info-edge:   #bed6ed;

  --ramp-1: #d4eef3;
  --ramp-2: #a8dde5;
  --ramp-3: #61bfd0;
  --ramp-4: #0d8fa3;
  --ramp-5: #0a7183;
  --ramp-6: #075361;

  --grid: #e8ecf3;
  --axis: #cdd4e0;

  --shadow-lg:  0 28px 64px -20px rgb(15 20 32 / 26%);
  --shadow-pop: 0 12px 28px -10px rgb(15 20 32 / 22%);
  /* On white a hairline alone leaves a panel floating with nothing under it, so light gets the
     faintest possible lift — enough to seat a card on the canvas, not enough to be seen as an
     effect. */
  --shadow-card: 0 1px 2px rgb(15 20 32 / 5%), 0 1px 1px -0.5px rgb(15 20 32 / 4%);

  /* The same eight hues re-stepped for a white surface, validated again as a set. Three of them
     sit below 3:1 here by design — the relief is that every mark is directly labelled and every
     chart ships a table view. */
  --series-1: #0d8fa3;
  --series-2: #4a3aa7;
  --series-3: #eb6834;
  --series-4: #1baf7a;
  --series-5: #eda100;
  --series-6: #e87ba4;
  --series-7: #2a78d6;
  --series-8: #e34948;
  --series-other: #97a1b3;
}

* { box-sizing: border-box; }

html, body { margin: 0; padding: 0; min-height: 100%; }

html {
  /* Safari on iPhone reflows text a size up when a phone is turned sideways. On a console full of
     aligned columns that is a layout change, not a kindness. */
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  font-family: var(--sans);
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Figures line up column-to-column — the whole app is comparison. */
  font-variant-numeric: tabular-nums;
  /* Checkboxes and radios are the one place the browser still paints its own blue. Handed the
     console's accent, a page of them stops looking like a form bolted onto an instrument. */
  accent-color: var(--accent);
}

/* The scrollbars are part of the panel, not the operating system's idea of one. */
* { scrollbar-color: var(--line-2) transparent; scrollbar-width: thin; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }

::-webkit-scrollbar-thumb {
  background: var(--line-2);
  border: 3px solid var(--canvas);
  border-radius: 999px;
}

::-webkit-scrollbar-thumb:hover { background: var(--ink-3); }

h1, h2, h3, h4 { margin: 0; line-height: 1.2; }

h2 { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
h3 { font-size: 13.5px; font-weight: 600; }

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

:focus-visible {
  outline: 2px solid var(--accent-hi);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

::selection { background: var(--accent-wash); color: var(--accent-hi); }

/* Android paints a grey flash over anything tapped. The console draws its own :active states, and
   two responses to one tap reads as lag. */
a, button, [role="button"], summary, label { -webkit-tap-highlight-color: transparent; }

/*
  The first stop for a keyboard, and invisible until it is asked for. Without it every visit to a
  page starts by tabbing through the whole navigation rail.
*/
.skip-link {
  position: fixed;
  top: -60px;
  left: var(--s3);
  z-index: 120;
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: #04161a;
  font-weight: 600;
  transition: top 0.15s var(--ease);
}

.skip-link:focus { top: calc(var(--s3) + env(safe-area-inset-top)); text-decoration: none; }

:root[data-theme="light"] .skip-link { color: #fff; }

/*
  The micro-label. Every one of these names a measured quantity, which is why they are set the way
  an instrument labels a dial: mono, uppercase, wide-tracked, small.
*/
.eyebrow,
.stat-label,
.nav-section,
.viz-unit,
.field label,
.idkey-name,
.signal-name,
.score-caption,
thead th {
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ---------- Boot screen ---------- */

.boot {
  min-height: 100vh;
  display: grid;
  place-content: center;
  text-align: center;
  gap: 14px;
  background: var(--canvas);
  color: var(--ink);
}

.boot-mark {
  font-family: var(--display);
  font-size: 30px;
  font-weight: 500;
  letter-spacing: -0.025em;
}

.boot-note {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* A single sweep under the mark, so the wait has a pulse rather than a spinner. */
.boot-bar {
  width: 132px;
  height: 2px;
  margin: 0 auto;
  border-radius: 999px;
  background: var(--surface-3);
  overflow: hidden;
}

.boot-bar::after {
  content: "";
  display: block;
  width: 30%;
  height: 100%;
  border-radius: 999px;
  background: var(--accent-hi);
  animation: sweep 1.15s var(--ease) infinite;
}

@keyframes sweep {
  0%   { transform: translateX(-110%); }
  100% { transform: translateX(440%); }
}

#blazor-error-ui {
  display: none;
  position: fixed;
  inset: auto 0 0 0;
  z-index: 200;
  padding: 12px 18px;
  background: var(--danger);
  color: #fff;
  font-size: 13px;
}
#blazor-error-ui .reload { color: #fff; text-decoration: underline; margin-left: 10px; }

/* ============================================================================
   SHELL — rail, topbar, working area
   ============================================================================ */

/* dvh so a phone's collapsing address bar does not leave a strip of canvas under the rail. */
.shell { display: flex; min-height: 100vh; min-height: 100dvh; }

.sidebar {
  width: var(--rail-w);
  flex: 0 0 var(--rail-w);
  background: var(--rail);
  border-right: 1px solid var(--line);
  color: var(--ink);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
}

/* The drawer's own dismiss. Off screen on a desk, where the rail is never covering anything. */
.drawer-close { display: none; }

.scrim {
  position: fixed;
  inset: 0;
  z-index: 85;
  background: rgb(4 6 10 / 58%);
  backdrop-filter: blur(3px);
  animation: fade 0.16s var(--ease);
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  height: var(--topbar-h);
  padding: 0 18px;
  border-bottom: 1px solid var(--line);
  flex: none;
}

/*
  The mark is four bars at descending heights — the shape of the demand curve this console exists
  to show, and the only piece of ornament in the app.
*/
.brand-glyph {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 18px;
  flex: none;
}

.brand-glyph i {
  display: block;
  width: 3px;
  border-radius: 1px;
  background: var(--accent);
}

.brand-glyph i:nth-child(1) { height: 100%; background: var(--accent-hi); }
.brand-glyph i:nth-child(2) { height: 68%; }
.brand-glyph i:nth-child(3) { height: 42%; }
.brand-glyph i:nth-child(4) { height: 24%; background: var(--violet); }

.brand-mark {
  font-family: var(--display);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--ink);
  line-height: 1.15;
}

.brand-sub {
  font-family: var(--mono);
  font-size: 8.5px;
  color: var(--ink-3);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  margin-top: 1px;
}

.nav { padding: 10px 10px 16px; flex: 1; overflow-y: auto; }

.nav-section { padding: 16px 10px 6px; }

.nav a {
  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 10px;
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  font-weight: 450;
  font-size: 13px;
  margin-bottom: 1px;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}

.nav a:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }

/* The one place glow is allowed to sit permanently: where you are. */
.nav a.active {
  background: var(--accent-wash);
  color: var(--ink);
  font-weight: 550;
  box-shadow: inset 2px 0 0 var(--accent-hi), 0 0 18px -6px var(--glow);
}

.nav a.active .nav-icon { color: var(--accent-hi); }

.nav-icon {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--ink-3);
  transition: color 0.15s var(--ease);
}

.nav-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.nav-count {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--review-wash);
  border: 1px solid var(--review-edge);
  color: var(--review);
  flex: none;
}

.rail-foot {
  padding: 12px 14px 14px;
  border-top: 1px solid var(--line);
  flex: none;
}

/* A hairline read-out of record health, always visible, never in the way. */
.rail-health {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  font-size: 11.5px;
  color: var(--ink-2);
  line-height: 1.4;
}

.rail-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ok);
  flex: none;
  margin-top: 5px;
}

.rail-dot.is-review { background: var(--review); }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* ---------- Topbar ---------- */

.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  /* The bar keeps its own height and lets the notch add to it, so nothing inside is ever clipped
     by the status bar on a phone held in portrait. */
  height: calc(var(--topbar-h) + env(safe-area-inset-top));
  padding-top: env(safe-area-inset-top);
  flex: none;
  display: flex;
  background: color-mix(in srgb, var(--canvas) 86%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}

/*
  The bar itself stays full-bleed — it is a surface, and a surface that stopped short of the window
  edge would read as a floating strip. What is constrained is the row of controls inside it, to the
  same measure the page uses. The padding lives here rather than on the bar so the two columns line
  up to the pixel: both are a border box of min(100%, --measure), centred, padded by the same 24px.

  Left as it was, on a wide monitor the account menu ended up at the far right of the window while
  the content it belongs to stopped hundreds of pixels short of it.
*/
.topbar-inner {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-right: max(24px, env(safe-area-inset-right));
  padding-left: max(24px, env(safe-area-inset-left));
}

/* A square control that carries only a glyph — the hamburger, the drawer's own close. */
.icon-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  flex: none;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}

.icon-btn svg { width: 20px; height: 20px; }
.icon-btn:hover { background: var(--surface-2); color: var(--ink); }
.icon-btn:active { background: var(--surface-3); }

/* Shown only once the rail has slid off screen and taken the name with it. */
.topbar-brand { display: none; align-items: center; gap: 9px; min-width: 0; }
.topbar-brand .brand-mark { font-size: 14px; }

.topbar-search {
  display: flex;
  align-items: center;
  gap: 9px;
  flex: 1;
  max-width: 400px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-3);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  text-align: left;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease);
}

.topbar-search:hover { border-color: var(--line-2); color: var(--ink-2); }
.topbar-search-label { flex: 1; }

.topbar-right { margin-left: auto; display: flex; align-items: center; gap: var(--s2); }

/* Who is signed in. On a shared campus machine this is the first thing anybody checks. */
.account { position: relative; }

.account-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px 4px 4px;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease);
}

.account-trigger:hover { border-color: var(--line-2); color: var(--ink); }

.account-initials {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  background: var(--accent-wash);
  color: var(--accent);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0;
}

.account-name { max-width: 15ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.account-sheet {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 50;
  min-width: 260px;
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.account-head { padding: 14px 16px; border-bottom: 1px solid var(--line); }
.account-name-full { font-weight: 500; color: var(--ink); }

.account-actions { display: flex; flex-direction: column; padding: 6px; }

.account-actions a,
.account-actions button {
  padding: 8px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.account-actions a:hover,
.account-actions button:hover { background: var(--surface-2); color: var(--ink); }

@media (max-width: 640px) { .account-name { display: none; } }

.theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s var(--ease), color 0.15s var(--ease), border-color 0.15s var(--ease);
}

.theme-toggle:hover { border-color: var(--line-2); color: var(--ink); }

.theme-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink-2);
  border: 1px solid var(--line-2);
  flex: none;
}

.theme-dot.is-dark { background: var(--surface-3); }

/*
  The working column.

  margin-inline is what stops the console hugging the left edge of a wide monitor. Without it the
  column sat against the rail and every pixel of slack piled up on the right — at 3440px that was
  half the working area, empty, with the account menu marooned a screen away from the content it
  belongs to. Centred, the slack is split and reads as a margin rather than as something missing.
*/
.page {
  padding: 28px max(24px, env(safe-area-inset-right)) 72px max(24px, env(safe-area-inset-left));
  max-width: var(--measure);
  width: 100%;
  margin-inline: auto;
}

/* ---------- The thumb bar ----------
   Hidden on a desk, where the rail is a better version of the same thing. */

.tabbar { display: none; }

.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 24px;
}

/* The page action aligns with the title, not with the bottom of a wrapped paragraph. */
.page-head > .btn,
.page-head > .btn-row,
.page-head > .field { margin-top: 20px; }

.eyebrow { margin-bottom: 9px; }
.eyebrow a { color: var(--ink-3); }
.eyebrow a:hover { color: var(--accent-hi); }

h1 {
  font-family: var(--display);
  font-size: 31px;
  font-weight: 500;
  letter-spacing: -0.026em;
  line-height: 1.1;
  /* A title that wraps should break into even lines rather than leave one word stranded. */
  text-wrap: balance;
}

/*
  Blazor focuses the <h1> on every navigation so screen readers announce the new page. That is
  worth keeping, but it is not a keyboard interaction, so it must not draw a focus box around
  every title. Keyboard users still get rings on the controls they actually operate.
*/
h1:focus, h1:focus-visible { outline: none; }

.page-sub {
  color: var(--ink-2);
  margin-top: 9px;
  max-width: 68ch;
  font-size: 13.5px;
  text-wrap: pretty;
}

/* ============================================================================
   PANELS
   ============================================================================ */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.card-head {
  padding: 13px 18px;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.card-body { padding: 18px; }

.grid { display: grid; gap: 16px; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }

/* Label-above-value facts, for the panels that report what a machine says about itself.
   Narrower minimum than .grid-3 because these are single words and short numbers — a firmware
   string or a count — and at 280px they would sit two to a row with most of the line empty. */
.kv-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px 16px;
}

.kv-grid > div > span { display: block; margin-bottom: 2px; }

/* ---------- Stat tiles ---------- */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(178px, 1fr));
  gap: 12px;
  margin-bottom: 20px;
}

.stat {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 15px 17px 16px;
  display: block;
  color: inherit;
  position: relative;
  overflow: hidden;
  transition: border-color 0.15s var(--ease), transform 0.15s var(--ease),
              box-shadow 0.15s var(--ease);
}

/* A hairline of the measure's own colour, so a row of tiles reads as a set of instruments. */
.stat::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  background: var(--stat-hue, transparent);
}

a.stat:hover {
  border-color: var(--line-2);
  text-decoration: none;
  transform: translateY(-1px);
  box-shadow: var(--shadow-pop);
}

/* A finger has no hover, so the press is the whole feedback. */
a.stat:active { transform: translateY(0) scale(0.995); }

.stat-value {
  font-family: var(--display);
  font-size: 34px;
  font-weight: 500;
  letter-spacing: -0.035em;
  font-variant-numeric: proportional-nums;
  margin-top: 7px;
  line-height: 1;
  color: var(--ink);
}

.stat-note { font-size: 12px; color: var(--ink-2); margin-top: 6px; line-height: 1.4; }

.stat.is-review { border-color: var(--review-edge); background: var(--review-wash); }
.stat.is-review .stat-value { color: var(--review); }
.stat.is-review .stat-label, .stat.is-review .stat-note { color: var(--review); opacity: 0.9; }

.stat.is-accent { --stat-hue: var(--accent-hi); }
.stat.is-violet { --stat-hue: var(--violet); }

/* ============================================================================
   CHARTS
   ============================================================================ */

.viz {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}

.viz-head {
  padding: 14px 18px 12px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.viz-title { font-size: 14px; font-weight: 600; letter-spacing: -0.005em; color: var(--ink); }
.viz-note { font-size: 12px; color: var(--ink-3); margin-top: 3px; max-width: 56ch; line-height: 1.45; }
.viz-body { padding: 0 18px 16px; flex: 1; min-width: 0; }

.viz-foot {
  padding: 10px 18px;
  border-top: 1px solid var(--line);
  font-size: 11.5px;
  color: var(--ink-3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

/* The headline figure a panel is really about, set at instrument scale. */
.viz-figure {
  font-family: var(--display);
  font-size: 30px;
  font-weight: 500;
  letter-spacing: -0.035em;
  font-variant-numeric: proportional-nums;
  line-height: 1;
  color: var(--ink);
}

.viz-figure-row { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.viz-unit { align-self: center; }

.viz-svg { display: block; width: 100%; overflow: visible; }

/* Recessive by construction: the data is the only thing meant to be read at a glance. */
.viz-grid { stroke: var(--grid); stroke-width: 1; }
.viz-axis { stroke: var(--axis); stroke-width: 1; }

.viz-tick {
  font-family: var(--mono);
  font-size: 9px;
  fill: var(--ink-3);
  letter-spacing: 0.04em;
}

.viz-label { font-size: 11px; fill: var(--ink-2); }
.viz-value { font-size: 11px; fill: var(--ink); font-weight: 600; }

/* ---------- Legend ---------- */

.legend { display: flex; flex-wrap: wrap; gap: 6px 16px; }

.legend-key {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--ink-2);
  white-space: nowrap;
}

.legend-swatch {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: var(--series, var(--series-1));
  flex: none;
}

.legend-value { color: var(--ink); font-weight: 600; }

/* ---------- Trend: a filled area under a 2px line ---------- */

.trend-wrap { position: relative; }

.trend-line {
  fill: none;
  stroke: var(--series-1);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* The second of three sanctioned glows — the demand line is the page's protagonist. */
.trend-glow {
  fill: none;
  stroke: var(--accent-hi);
  stroke-width: 5;
  opacity: 0.15;
  stroke-linejoin: round;
}

.trend-dot {
  fill: var(--surface);
  stroke: var(--series-1);
  stroke-width: 2;
}

.trend-peak { fill: var(--series-1); }

/* Hit targets are the full column height, so a tooltip is easy to catch. */
.trend-hit { fill: transparent; cursor: crosshair; }

.trend-cursor {
  stroke: var(--line-2);
  stroke-width: 1;
  stroke-dasharray: 3 3;
  pointer-events: none;
}

/* ---------- Tooltip ---------- */

.viz-tip {
  position: absolute;
  z-index: 20;
  pointer-events: none;
  transform: translate(-50%, -100%);
  margin-top: -12px;
  padding: 8px 11px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-2);
  background: var(--surface-2);
  box-shadow: var(--shadow-pop);
  font-size: 12px;
  color: var(--ink);
  white-space: nowrap;
  line-height: 1.5;
}

.viz-tip-label {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: block;
}

.viz-tip-value { font-weight: 600; }

/* ---------- Donut ---------- */

.donut-wrap { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; }

.donut { flex: none; }

/* butt caps plus a gap in the dash pattern give the 2px surface gap between segments, so the ring
   reads as parts rather than a gradient. */
.donut-seg {
  fill: none;
  stroke-width: 14;
  stroke-linecap: butt;
  transition: stroke-width 0.15s var(--ease);
}

.donut-seg:hover { stroke-width: 17; }

.donut-centre-value {
  font-family: var(--display);
  font-size: 23px;
  font-weight: 500;
  letter-spacing: -0.035em;
  fill: var(--ink);
}

.donut-centre-label {
  font-family: var(--mono);
  font-size: 8px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  fill: var(--ink-3);
}

.donut-keys { flex: 1; min-width: 160px; display: flex; flex-direction: column; gap: 9px; }

.donut-key {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 9px;
  font-size: 12.5px;
}

.donut-key-name { color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.donut-key-value { color: var(--ink); font-weight: 600; }
.donut-key-pct { font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); min-width: 38px; text-align: right; }

/* ---------- Ranked bars ---------- */

.bars { display: flex; flex-direction: column; gap: 12px; }

.bar-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 12px;
  align-items: baseline;
  font-size: 12.5px;
  margin-bottom: 5px;
}

.bar-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink-2); }
.bar-count { font-weight: 600; font-variant-numeric: tabular-nums; color: var(--ink); white-space: nowrap; }
.bar-share { font-family: var(--mono); font-size: 10px; color: var(--ink-3); margin-left: 7px; font-weight: 400; }

.bar-track {
  height: 7px;
  border-radius: 999px;
  background: var(--surface-3);
  overflow: hidden;
}

/* 4px rounded data-end, anchored to the baseline. */
.bar-fill {
  height: 100%;
  background: var(--series, var(--series-1));
  border-radius: 0 999px 999px 0;
  min-width: 3px;
  transition: width 0.5s var(--ease);
}

/* A gap in the data is not a category, so it is reported under the chart rather than given a bar
   there — see CountBars. A folded tail ("Other, 2 more") keeps its bar in the neutral slot. */

/* ---------- Stacked composition ---------- */

.stack {
  display: flex;
  gap: 2px;
  height: 12px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--surface-3);
}

.stack-seg {
  background: var(--series, var(--series-1));
  min-width: 2px;
  transition: flex-grow 0.4s var(--ease);
}

/* ---------- Seat pressure: the signature panel ----------
   A bullet chart. The thick violet bar is what a campus can seat; the thin cyan bar over it is
   what people asked of it. Where the cyan runs past the end of the violet, that overhang is the
   number of people who cannot be given a place — the only measure on this screen that decides
   anything, and the only one allowed to glow. */

.press { display: flex; flex-direction: column; gap: 15px; }

.press-row { display: grid; gap: 6px; }

.press-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }

.press-name {
  font-size: 13px;
  font-weight: 550;
  color: var(--ink);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.press-city { color: var(--ink-3); font-weight: 400; font-size: 11.5px; margin-left: 7px; }

.press-ratio {
  font-family: var(--display);
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.025em;
  color: var(--accent-hi);
  white-space: nowrap;
  flex: none;
}

.press-ratio.is-unknown {
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 400;
  color: var(--ink-3);
}

.press-track {
  position: relative;
  height: 16px;
  border-radius: var(--radius-xs);
  background: var(--surface-3);
  overflow: hidden;
}

/* Capacity: the seats a campus actually has, full height and anchored at the left. */
.press-capacity {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--violet);
  border-radius: var(--radius-xs);
  min-width: 2px;
}

/* Demand: a thinner measure drawn over capacity, so the two are read against one baseline
   rather than as two separate bars. */
.press-demand {
  position: absolute;
  top: 5px;
  height: 6px;
  left: 0;
  background: var(--accent);
  border-radius: 0 999px 999px 0;
  min-width: 3px;
  transition: width 0.5s var(--ease);
}

/* With no capacity recorded anywhere there is nothing to overlay, so demand takes the whole
   track and the panel reads as a plain ranking. */
.press-demand.is-solo {
  top: 0;
  height: auto;
  bottom: 0;
  background: var(--accent);
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
}

/* The third and last sanctioned glow — and only when demand has actually outrun the seats. */
.press-demand.is-over {
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-hi) 100%);
  box-shadow: 0 0 14px -1px var(--glow);
}

/* A 2px surface gap at the capacity mark, so "where the seats run out" is readable to the pixel. */
.press-mark {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: var(--surface);
}

/* A row of short supporting facts under a chart — the numbers that qualify what was just drawn. */
.factrow { display: flex; gap: 18px; flex-wrap: wrap; font-size: 11.5px; color: var(--ink-3); }
.factrow b { color: var(--ink-2); font-weight: 600; }

/* ---------- Nested bars ----------
   Two measures where one contains the other — applications naming a course anywhere in their three
   preference slots, and the subset that named it first. A wide bar with a narrower one drawn over
   it on the same baseline, because that containment is the point; two bars side by side would
   invite adding them together. */

.nest { display: flex; flex-direction: column; gap: 13px; }

.nest-track {
  position: relative;
  height: 16px;
  border-radius: var(--radius-xs);
  background: var(--surface-3);
  overflow: hidden;
}

.nest-wide {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--series-2);
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
  min-width: 2px;
  transition: width 0.5s var(--ease);
}

.nest-narrow {
  position: absolute;
  top: 5px;
  bottom: 5px;
  left: 0;
  background: var(--series-1);
  border-radius: 0 999px 999px 0;
  min-width: 3px;
  transition: width 0.5s var(--ease);
}

/* ---------- Table fallback ----------
   Every chart can be read as numbers. This is the relief for the light-theme series that sit
   below 3:1 against white, and it is what gets read out over the phone. */

.viz-table-toggle {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 11.5px;
  color: var(--ink-3);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.viz-table-toggle:hover { color: var(--accent-hi); }

/* ============================================================================
   TABLES
   ============================================================================ */

.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-x: contain; }

table { width: 100%; border-collapse: collapse; font-size: 13px; }

thead th {
  text-align: left;
  padding: 9px 16px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 2;
}

th.sortable { cursor: pointer; user-select: none; transition: color 0.12s var(--ease); }
th.sortable:hover { color: var(--ink); }

.sort-caret { color: var(--accent-hi); margin-left: 3px; }

/* Compact ≈ 36px rows, default ≈ 44px, comfortable ≈ 56px. */
:root[data-density="compact"]     { --row-pad-y: 7px;  --row-font: 12.5px; }
:root[data-density="comfortable"] { --row-pad-y: 17px; --row-font: 13.5px; }

tbody td {
  padding: var(--row-pad-y) 16px;
  font-size: var(--row-font);
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}

/* Compact hides the secondary sub-lines so a row really is one line tall. */
:root[data-density="compact"] tbody td .row-secondary { display: none; }

tbody tr:last-child td { border-bottom: none; }
tbody tr { transition: background 0.1s var(--ease); }
tbody tr:hover { background: var(--surface-2); }

td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.nowrap { white-space: nowrap; }

/*
  A cell holding one long unbroken run of characters — an email address, a URL. Two things have to
  be true at once and they pull against each other.

  It has to be allowed to break mid-token, because "m.bilal.ahmed.karachi@example.com" has no
  spaces in it and would otherwise hold a column open at its full 357px. `overflow-wrap: anywhere`
  does that, but it also tells the table's auto-layout that this column's minimum is one character
  wide — so when columns compete, this is the one that gives, and it gives everything. On the
  student register with every field on show that left the email 68px wide: eleven characters a
  line, five lines deep, a ribbon rather than an address.

  The floor is what makes `anywhere` safe — the column can still be the first to give, but only
  down to something a person can read. It is set against the width available because the two are
  not independent: the floor is only ever reached when columns are competing, and how hard they
  compete is exactly what the viewport decides.

    · Below 1200px the floor stays low enough that it is never the thing that pushes a table into
      a horizontal scroll it would not otherwise need.
    · At 1200px and up the roomier floor costs nothing to the modes that fit, because there the
      column is not being squeezed at all and the floor never binds. It is paid for only by the
      fifteen-column view, which overflows the panel whatever this is set to — so the scrollbar is
      already there, and the choice is just whether the email in it can be read.
*/
.cell-wrap {
  overflow-wrap: anywhere;
  min-width: 12ch;
}

@media (min-width: 1200px) {
  .cell-wrap { min-width: 20ch; }
}

.mono {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: -0.01em;
}

.muted { color: var(--ink-2); }
.faint { color: var(--ink-3); }
.small { font-size: 12px; }

/* ============================================================================
   CHIPS
   ============================================================================ */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  border: 1px solid transparent;
  white-space: nowrap;
  line-height: 1.7;
}

.chip::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.chip-ok       { background: var(--ok-wash);     color: var(--ok);     border-color: var(--ok-edge); }
.chip-review   { background: var(--review-wash); color: var(--review); border-color: var(--review-edge); }
.chip-conflict { background: var(--danger-wash); color: var(--danger); border-color: var(--danger-edge); }
.chip-info     { background: var(--info-wash);   color: var(--info);   border-color: var(--info-edge); }
.chip-plain    { background: var(--surface-2);   color: var(--ink-2);  border-color: var(--line); }
.chip-plain::before { background: var(--ink-3); }

/* A bare count is not a status, so it carries no status dot. */
.chip-count {
  background: var(--surface-2);
  color: var(--ink-2);
  border-color: var(--line);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.chip-count::before { display: none; }

/* Identity strip — the evidence this whole system turns on, shown rather than summarised. */
.idkeys { display: flex; gap: 5px; flex-wrap: wrap; }

.idkey {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 2px 8px;
  border-radius: var(--radius-xs);
  border: 1px solid var(--line);
  background: var(--surface-2);
  font-size: 11px;
}

.idkey-name { font-size: 8.5px; letter-spacing: 0.12em; }
.idkey-value { font-family: var(--mono); font-size: 11.5px; color: var(--ink); letter-spacing: -0.01em; }

.idkey.is-primary { background: var(--accent-wash); border-color: var(--accent-edge); }
.idkey.is-primary .idkey-name { color: var(--accent-hi); }
.idkey.is-missing { opacity: 0.6; }
.idkey.is-missing .idkey-value { color: var(--ink-3); font-style: italic; }

/* ============================================================================
   CONTROLS
   ============================================================================ */

/* Holds the space a photograph will fill while its bytes are being fetched, so a grid of them does
   not jump as each one lands. */
.photo-unavailable {
  display: grid;
  place-items: center;
  min-height: 120px;
  padding: 12px;
  text-align: center;
  background: var(--surface-2, var(--surface));
  border-radius: var(--radius-sm);
}

/* A button that reads as a link.

   Downloads cannot be anchors here — the file comes back from an endpoint that needs a bearer
   token, so the bytes are fetched in code and handed to the browser as a blob. Where the old markup
   had a filename as a link in a list, this keeps that appearance on the button that replaced it. */
.btn-link {
  border: 0;
  padding: 0;
  background: none;
  font: inherit;
  color: var(--accent);
  text-decoration: underline;
  cursor: pointer;
}

.btn-link:hover:not(:disabled) { color: var(--accent-strong, var(--accent)); }
.btn-link:disabled { cursor: default; opacity: 0.6; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.13s var(--ease), border-color 0.13s var(--ease), color 0.13s var(--ease),
              transform 0.08s var(--ease);
}

.btn:hover:not(:disabled) { background: var(--surface-2); border-color: var(--ink-3); }
.btn:active:not(:disabled) { background: var(--surface-3); transform: scale(0.98); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* Cyan is bright enough on the dark plane that dark ink on it beats white for contrast. */
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #04161a;
  font-weight: 600;
}
.btn-primary:hover:not(:disabled) { background: var(--accent-hi); border-color: var(--accent-hi); color: #04161a; }

:root[data-theme="light"] .btn-primary,
:root[data-theme="light"] .btn-primary:hover:not(:disabled) { color: #fff; }

.btn-danger { color: var(--danger); border-color: var(--danger-edge); }
.btn-danger:hover:not(:disabled) { background: var(--danger-wash); border-color: var(--danger); }

.btn-sm { padding: 4px 10px; font-size: 12px; }
.btn-row { display: flex; gap: 8px; flex-wrap: wrap; }

/* Row actions stay on one line so table rows keep a scannable height. */
td .btn-row { flex-wrap: nowrap; }

.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }

input[type="text"], input[type="search"], input[type="number"], input[type="date"],
input[type="email"], input[type="password"], input[type="tel"], select, textarea {
  padding: 7px 11px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  min-width: 0;
  width: 100%;
  transition: border-color 0.13s var(--ease), box-shadow 0.13s var(--ease);
}

/*
  The browser's own select is the last control still drawn by the operating system, and on a screen
  this deliberately styled it shows. The chevron is redrawn to match — the picker itself is left
  native, which is what a phone should open.
*/
select {
  -webkit-appearance: none;
  appearance: none;
  padding-right: 30px;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 15px center, right 10px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}

/* width:auto is set inline in a few places (the rows-per-page picker); the chevron still needs
   somewhere to sit. */
select:not([size]) { min-width: 4.5em; }

input:focus, select:focus, textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
  outline: none;
}

input::placeholder { color: var(--ink-3); }

textarea { line-height: 1.5; resize: vertical; }

/* Row selection is a target, and the default 13px box is a hard one to hit even with a mouse. */
input[type="checkbox"], input[type="radio"] {
  width: 16px;
  height: 16px;
  margin: 0;
  cursor: pointer;
  flex: none;
}

.filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: 13px;
  align-items: end;
}

/* Context about the campus being filtered on, sitting under the filters it belongs to.
   Wraps rather than scrolls: it is a handful of short facts, not a table. */
.campus-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 28px;
  margin-top: 14px;
  padding-top: 13px;
  border-top: 1px solid var(--line);
  font-size: 13px;
}

/* ============================================================================
   THE HANDOVER REPORT
   ============================================================================
   A document, not a screen. It carries hairlines and flat surfaces so it survives being printed on
   an office laser printer, and it prints in light regardless of the console's theme — a dark page
   would empty a toner cartridge and read worse on paper. */

.report { max-width: 1000px; }

.report-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  padding-bottom: 16px;
  border-bottom: 2px solid var(--ink);
  margin-bottom: 20px;
}

.report-title {
  font-family: var(--display);
  font-size: 25px;
  font-weight: 500;
  letter-spacing: -0.025em;
  margin-top: 4px;
}

.report-score { text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 7px; }

.report-pct {
  font-family: var(--display);
  font-size: 38px;
  font-weight: 500;
  letter-spacing: -0.035em;
  line-height: 1;
}

.report-section {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  margin: 28px 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--line-2);
}

.report-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
  margin-bottom: 6px;
}

.report-table th,
.report-table td {
  padding: 7px 10px;
  border: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
  position: static;
  background: none;
  text-transform: none;
  letter-spacing: normal;
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

.report-table thead th,
.report-table tbody th {
  background: var(--surface-2);
  font-weight: 600;
  color: var(--ink-2);
  white-space: nowrap;
}

.report-table td.num, .report-table th.num { text-align: right; }
.report-table tbody tr:hover { background: none; }

.report-domain td {
  background: var(--surface-3);
  font-weight: 600;
  font-size: 12px;
}

.report-total td { background: var(--surface-2); }
.report-ratings td:nth-child(2) { line-height: 1.4; }

.report-prose {
  margin: 6px 0 0;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Free text typed into a box, shown back the way it was written. Without this the line breaks are
   collapsed and a list of separate remarks reads as one sentence. */
.prose-lines {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.report-foot {
  margin-top: 28px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  font-size: 11px;
  color: var(--ink-3);
}

@media print {
  /* Paper is white whatever the console is set to. */
  :root, :root[data-theme="dark"] {
    --canvas: #ffffff;
    --surface: #ffffff;
    --surface-2: #f2f2f2;
    --surface-3: #e6e6e6;
    --line: #cccccc;
    --line-2: #999999;
    --ink: #000000;
    --ink-2: #333333;
    --ink-3: #666666;
  }

  .sidebar, .topbar, .no-print, .viz-table-toggle { display: none !important; }
  .shell { display: block; }
  .page { padding: 0; max-width: none; }
  body { background: #fff; font-size: 11pt; }

  /* A row of a findings table split across a page break is unreadable. */
  .report-table tr, .photo, figure { break-inside: avoid; }
  .report-section { break-after: avoid; }
  .photos { grid-template-columns: repeat(2, 1fr); }

  a { color: inherit; text-decoration: none; }
}

/* ============================================================================
   THE PUBLIC PAGE
   ============================================================================
   What a student sees when they follow a feedback link. Wider spacing and larger targets than the
   console: this is somebody using the system once, on a phone, not an operator who lives in it. */

.public-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--canvas);
}

.public-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 18px 22px;
  border-bottom: 1px solid var(--line);
}

.public-main {
  flex: 1;
  width: 100%;
  max-width: 680px;
  margin-inline: auto;
  padding: 32px 20px 56px;
}

.public-card { background: var(--surface); }
.public-card .card-body { padding: 28px; }

.public-title {
  font-family: var(--display);
  font-size: 26px;
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.2;
  margin-bottom: 10px;
}

.public-foot {
  padding: 20px 22px 32px;
  text-align: center;
  font-size: 11.5px;
  color: var(--ink-3);
}

.question-text { font-size: 15px; line-height: 1.45; color: var(--ink); }

/* Urdu is set in Nastaliq, the hand it is read in; its tall ascenders and descenders need far more
   line height than Latin text, or lines collide. The font is only fetched once a page shows Urdu —
   Google serves it split by unicode-range. */
.urdu { font-family: "Noto Nastaliq Urdu", var(--sans); line-height: 2; }
.question-text.urdu { margin-top: 2px; font-size: 16px; text-align: right; }
input.urdu { line-height: 1.8; }
.scale-label.urdu { font-size: 10px; line-height: 1.8; }

/* A note read right to left keeps its accent stripe on the side the reader starts from. */
.note[dir="rtl"] { border-left: 1px solid var(--line); border-right: 3px solid var(--ink-3); font-size: 14px; }

.scale-ends { display: none; }

/* The 1–5 scale, with every point labelled. A row of bare numbers makes people guess which end is
   good, and they guess differently. */
.scale {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 6px;
  margin-top: 12px;
}

.scale button {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 11px 4px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  cursor: pointer;
  transition: background 0.12s var(--ease), border-color 0.12s var(--ease), color 0.12s var(--ease);
}

.scale button:hover { border-color: var(--ink-3); color: var(--ink); }

.scale button.is-set {
  background: var(--accent);
  border-color: var(--accent);
  color: #04161a;
}

:root[data-theme="light"] .scale button.is-set { color: #fff; }

.scale-value { font-family: var(--display); font-size: 17px; font-weight: 500; }
.scale-label { font-size: 10px; line-height: 1.2; text-align: center; }

@media (max-width: 520px) {
  .scale-label { display: none; }
  .scale-ends {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin-top: 6px;
    font-size: 11px;
    color: var(--ink-3);
  }
  .public-card .card-body { padding: 20px; }
}

/* ---------- Photo evidence ----------
   A contact sheet, not a gallery: these are evidence behind a rating, so they are shown small
   enough to scan and each one carries what it is a picture of. */

.photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 14px;
}

.photo {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface-2);
}

.photo img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: var(--surface-3);
}

.photo figcaption {
  padding: 9px 11px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 11.5px;
}

.photo-caption { color: var(--ink); line-height: 1.35; overflow-wrap: anywhere; }

.photo-meta {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ---------- Report tabs ----------
   Underlined rather than boxed: these switch the subject of a page, they do not filter it, and a
   pill group would read as one more control among the filters. */

.tabs {
  display: flex;
  gap: var(--s5);
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--s6);
}

.tab {
  position: relative;
  padding: 0 2px 11px;
  color: var(--ink-3);
  font-size: 13.5px;
  font-weight: 500;
  white-space: nowrap;
  transition: color 0.15s var(--ease);
}

.tab:hover { color: var(--ink); text-decoration: none; }

.tab.is-active { color: var(--ink); font-weight: 600; }

.tab.is-active::after {
  content: "";
  position: absolute;
  inset: auto 0 -1px 0;
  height: 2px;
  border-radius: 2px 2px 0 0;
  background: var(--accent-hi);
}

/* ---------- QA scoring ----------
   A benchmark is scored 0–4 against the framework. The scale is short and fixed, so it is laid out
   as five targets rather than hidden in a dropdown: the visiting team is scoring 41 of these in a
   sitting, and every extra click is paid 41 times. */

.rate { display: inline-flex; gap: 3px; }

.rate button {
  width: 30px;
  height: 28px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-xs);
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.12s var(--ease), color 0.12s var(--ease), border-color 0.12s var(--ease);
}

.rate button:hover:not(:disabled) { border-color: var(--ink-3); color: var(--ink); }
.rate button:disabled { cursor: default; opacity: 0.75; }

/* The chosen score. Zero is a finding, not an absence, so it is marked as firmly as a four —
   just in the colour the framework's own bands would give it. */
.rate button.is-set {
  background: var(--accent);
  border-color: var(--accent);
  color: #04161a;
  font-weight: 600;
}

.rate button.is-set.is-zero { background: var(--danger); border-color: var(--danger); color: #fff; }
.rate button.is-set.is-low  { background: var(--review); border-color: var(--review); color: #201900; }

:root[data-theme="light"] .rate button.is-set { color: #fff; }
:root[data-theme="light"] .rate button.is-set.is-low { color: #fff; }

/* One benchmark: the statement, its provenance, the score and the justification. */
.bench {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
}

.bench:last-child { border-bottom: none; }
.bench-text { font-size: 13px; color: var(--ink); line-height: 1.45; }

.bench-meta {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.bench-controls { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.bench-controls input { flex: 1; min-width: 220px; }

/* A benchmark nobody has scored yet — distinct from one scored zero. */
.bench.is-unrated { background: color-mix(in srgb, var(--review-wash) 45%, transparent); }

.domain-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  padding: 12px 18px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
}

.domain-title { font-size: 13px; font-weight: 600; color: var(--ink); }

.domain-score {
  font-family: var(--display);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink-2);
  white-space: nowrap;
}

/* The framework's three verdicts. */
.band {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 11px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.band-excellent    { background: var(--ok-wash);     color: var(--ok);      border-color: var(--ok-edge); }
.band-satisfactory { background: var(--review-wash); color: var(--review);  border-color: var(--review-edge); }
.band-critical     { background: var(--danger-wash); color: var(--danger);  border-color: var(--danger-edge); }

/* ---------- A quality assurance visit, as an inspection report ----------
   The page is a scoresheet with evidence around it. It opens on the seal — the overall score as a
   ring, with the framework's own band thresholds marked on it — and everything after is quieter:
   numbered domains, checklists, and the sheet itself. */

.qa-hero {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 26px;
  align-items: center;
  padding: 22px 24px;
}

.qa-seal { position: relative; width: 148px; height: 148px; flex: none; }
.qa-seal svg { display: block; width: 100%; height: 100%; transform: rotate(-90deg); }
.qa-seal-track { fill: none; stroke: var(--surface-3); stroke-width: 10; }
.qa-seal-arc { fill: none; stroke: var(--qa-band, var(--accent-hi)); stroke-width: 10; stroke-linecap: round; transition: stroke-dasharray 0.6s var(--ease); }
.qa-seal-tick { stroke: var(--ink-3); stroke-width: 2; }

.qa-seal-value {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  text-align: center;
  line-height: 1;
}

.qa-seal-number {
  font-family: var(--display);
  font-size: 34px;
  font-weight: 500;
  letter-spacing: -0.04em;
  color: var(--ink);
}

.qa-seal-number small { font-size: 17px; letter-spacing: 0; color: var(--ink-2); margin-left: 1px; }
.qa-seal-caption { font-size: 11.5px; color: var(--ink-2); margin-top: 6px; }

.qa-seal.is-excellent    { --qa-band: var(--ok); }
.qa-seal.is-satisfactory { --qa-band: var(--review); }
.qa-seal.is-critical     { --qa-band: var(--danger); }
.qa-seal.is-draft        { --qa-band: var(--accent-hi); }

.qa-hero-title { font-family: var(--display); font-size: 27px; font-weight: 500; letter-spacing: -0.025em; line-height: 1.15; }

.qa-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px 22px;
  margin: 14px 0 0;
}

.qa-facts dt { font-size: 11.5px; color: var(--ink-3); }
.qa-facts dd { margin: 3px 0 0; font-size: 13.5px; color: var(--ink); font-weight: 500; }

.qa-delta { font-weight: 600; }
.qa-delta.is-up { color: var(--ok); }
.qa-delta.is-down { color: var(--danger); }

/* The six domains, numbered as the framework numbers them. */
.qa-domains {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 0 28px;
  padding: 6px 24px 18px;
  border-top: 1px solid var(--line);
}

.qa-domain {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px 12px;
  padding: 12px 0 4px;
}

.qa-num {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-family: var(--display);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
}

.qa-domain-name { font-size: 13px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qa-domain-score { font-size: 12px; color: var(--ink-2); white-space: nowrap; font-variant-numeric: tabular-nums; }
.qa-domain-score b { font-family: var(--display); font-size: 15px; font-weight: 500; color: var(--ink); margin-right: 6px; }
.qa-domain .bar-track { grid-column: 2 / 4; }

/* The section bar: a long page, kept navigable. */
.qa-nav {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  gap: 4px;
  overflow-x: auto;
  margin: 0 0 18px;
  padding: 8px 0;
  background: var(--canvas);
  border-bottom: 1px solid var(--line);
  scrollbar-width: none;
}

.qa-nav a {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 11px;
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  font-size: 13px;
  text-decoration: none;
}

.qa-nav a:hover { background: var(--surface-3); color: var(--ink); text-decoration: none; }
.qa-nav-count { font-size: 11px; font-weight: 600; padding: 1px 7px; border-radius: 999px; background: var(--review-wash); color: var(--review); border: 1px solid var(--review-edge); }

.qa-section { scroll-margin-top: 64px; }

.qa-section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 26px 2px 10px; }
.qa-section-head h2 { font-family: var(--display); font-size: 18px; font-weight: 500; letter-spacing: -0.02em; }

/* Evidence, as a checklist: met, not met, or cannot tell. */
.qa-checks { display: grid; gap: 2px; }

.qa-check {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  align-items: start;
  gap: 10px;
  padding: 7px 0;
  font-size: 13px;
  color: var(--ink);
  line-height: 1.4;
}

.qa-check + .qa-check { border-top: 1px dashed var(--line); }

.qa-check-mark {
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  margin-top: -1px;
}

.qa-check.is-met .qa-check-mark     { background: var(--ok-wash); color: var(--ok); border: 1px solid var(--ok-edge); }
.qa-check.is-not-met .qa-check-mark { background: var(--danger-wash); color: var(--danger); border: 1px solid var(--danger-edge); }
.qa-check.is-unknown .qa-check-mark { background: var(--surface-2); color: var(--ink-3); border: 1px solid var(--line); }
.qa-check.is-unknown { color: var(--ink-2); }

/* Chips that are information rather than a verdict still sit in the list's rhythm. */
.qa-checks > .chip { justify-self: start; margin: 6px 0; }

/* The scoring sheet's domain heading: number, name, and how much of it is scored. */
.qa-sheet-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 13px 18px; background: var(--surface-2); border-bottom: 1px solid var(--line); }
.qa-sheet-head .qa-num { background: var(--surface); }
.qa-sheet-title { font-size: 14px; font-weight: 600; color: var(--ink); flex: 1; min-width: 180px; }
.qa-sheet-progress { display: flex; align-items: center; gap: 10px; font-size: 12px; color: var(--ink-2); white-space: nowrap; }
.qa-sheet-progress .bar-track { width: 90px; }

/* Drafts keep their controls in reach down a long sheet. */
.qa-actionbar {
  position: sticky;
  bottom: 0;
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 20px;
  padding: 12px 18px;
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: var(--shadow-pop);
}

.qa-actionbar-progress { flex: 1; min-width: 200px; font-size: 13px; color: var(--ink-2); }
.qa-actionbar-progress .bar-track { margin-top: 6px; max-width: 360px; }

@media (max-width: 640px) {
  .qa-hero { grid-template-columns: 1fr; justify-items: start; padding: 18px; gap: 18px; }
  .qa-seal { width: 124px; height: 124px; }
  .qa-seal-number { font-size: 28px; }
  .qa-domains { padding: 4px 18px 14px; grid-template-columns: 1fr; }
  .qa-hero-title { font-size: 23px; }
}

@media (prefers-reduced-motion: reduce) {
  .qa-seal-arc { transition: none; }
}

/* ---------- Density switch ---------- */

.density {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.density button {
  border: none;
  background: var(--surface);
  color: var(--ink-3);
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 5px 10px;
  cursor: pointer;
  border-right: 1px solid var(--line);
}

.density button:last-child { border-right: none; }
.density button:hover { background: var(--surface-2); color: var(--ink); }
.density button.is-active { background: var(--accent-wash); color: var(--accent-hi); }

/* ============================================================================
   MESSAGES & STATES
   ============================================================================ */

.note {
  padding: 12px 15px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface-2);
  font-size: 13px;
  margin-bottom: 16px;
  border-left: 3px solid var(--ink-3);
}

.note-review   { background: var(--review-wash); border-color: var(--review-edge); border-left-color: var(--review); color: var(--review); }
.note-conflict { background: var(--danger-wash); border-color: var(--danger-edge); border-left-color: var(--danger); color: var(--danger); }
.note-ok       { background: var(--ok-wash);     border-color: var(--ok-edge);     border-left-color: var(--ok);     color: var(--ok); }
.note-info     { background: var(--info-wash);   border-color: var(--info-edge);   border-left-color: var(--info);   color: var(--info); }

.note strong { font-weight: 650; }
.note a { color: inherit; text-decoration: underline; }

/*
  The answer to a looked-up number.

  Written as a paragraph first, inside a .note-review, and it read badly for two reasons that are
  worth separating. The colour was one: .note-review paints its whole contents amber, which suits a
  one-line warning and turns sixty words into a block nobody reads. The shape was the other — the
  campus, the coordinator and their phone number were facts buried mid-sentence, where they cannot
  be scanned for, copied cleanly, or found twice in a row.

  So the panel is plain, and the facts carry labels. Nothing here is a warning: the number resolved,
  and what follows is who holds them and what can be done about it.
*/
.lookup-number {
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: .01em;
}

.lookup-situation {
  margin-top: 2px;
  font-size: 13px;
  color: var(--ink-2);
}

/* Label above value rather than beside it: a campus name here runs to sixty characters, and a
   two-column grid either wraps it into a ragged column or squeezes the label to nothing. */
.lookup-facts {
  margin: 12px 0 0;
  display: grid;
  gap: 8px 20px;
  grid-template-columns: repeat(auto-fit, minmax(180px, max-content));
}

.lookup-facts dt {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-3);
}

.lookup-facts dd {
  margin: 2px 0 0;
  font-size: 13px;
  color: var(--ink);
}

.lookup-note {
  margin-top: 12px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-2);
}

/*
  A worked example that stays put.

  Recessed a step below the field's own help text — it is the least important line in the group and
  must not compete with the requirement above it — but it is real text on the page rather than a
  placeholder, because a placeholder disappears at the exact moment somebody starts writing and
  needs it.
*/
.field-example {
  margin-top: 6px;
  padding-left: 9px;
  border-left: 2px solid var(--line);
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-3);
}

.field-example em { font-style: italic; color: var(--ink-2); }

/*
  A finding on the dashboard.

  A row rather than a table: there are four of them, the full list has its own screen, and a table
  header over four rows is furniture. The chip is fixed-width enough to form a column by itself, so
  the headlines still line up.
*/
.finding-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.finding {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.finding-body { flex: 1; min-width: 0; }

.finding-headline {
  font-size: 13px;
  color: var(--ink);
  margin-bottom: 2px;
}

/* The chip and the button hold their size; only the middle gives way. */
.finding > .chip,
.finding > .btn { flex: none; }

/* Stacked on a phone: three columns across 360px leaves the headline a word wide. */
@media (max-width: 560px) {
  .finding { flex-wrap: wrap; }
  .finding-body { flex-basis: 100%; order: 2; }
  .finding > .btn { order: 3; }
}

/* ---------- Needs attention: a to-do list, split by what waiting costs ----------
   Each group is one card with a coloured spine down its left edge — red for "fix today", amber
   for "this week" — so the two halves read apart before a word is read. Rows are sentences, and the
   button names the job. */

.at-tally { display: flex; flex-wrap: wrap; gap: 8px 22px; margin: 0 2px 18px; font-size: 14px; color: var(--ink-2); }
.at-tally-part { display: inline-flex; align-items: baseline; gap: 7px; }
.at-tally-part::before { content: ""; width: 9px; height: 9px; border-radius: 50%; background: var(--review); align-self: center; }
.at-tally-part.is-urgent::before { background: var(--danger); }
.at-tally-part b { font-family: var(--display); font-size: 22px; font-weight: 500; color: var(--ink); letter-spacing: -0.02em; }

.at-group {
  --at-tone: var(--review);
  --at-wash: var(--review-wash);
  position: relative;
  margin-bottom: 18px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.at-group::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 4px; background: var(--at-tone); }
.at-group.is-urgent { --at-tone: var(--danger); --at-wash: var(--danger-wash); }

.at-group-head {
  display: flex;
  align-items: baseline;
  gap: 6px 14px;
  flex-wrap: wrap;
  padding: 14px 20px 12px 24px;
  background: var(--at-wash);
  border-bottom: 1px solid var(--line);
}

.at-group-head h2 { font-family: var(--display); font-size: 17px; font-weight: 500; letter-spacing: -0.015em; color: var(--at-tone); }

.at-list { list-style: none; margin: 0; padding: 0; }

.at-item {
  display: flex;
  align-items: center;
  gap: 12px 24px;
  padding: 16px 20px 16px 24px;
}

.at-item + .at-item { border-top: 1px solid var(--line); }
.at-body { flex: 1; min-width: 0; }

.at-kind { font-size: 12px; font-weight: 500; color: var(--at-tone); }
.at-headline { font-size: 15px; font-weight: 600; color: var(--ink); line-height: 1.35; margin-top: 2px; }
.at-detail { margin: 5px 0 0; font-size: 13px; color: var(--ink-2); line-height: 1.5; max-width: 72ch; }
.at-item > .btn { flex: none; }

.at-clear { display: flex; align-items: flex-start; gap: 18px; padding: 24px; }
.at-clear h2 { font-family: var(--display); font-size: 19px; font-weight: 500; letter-spacing: -0.015em; }
.at-clear p { margin: 6px 0 0; color: var(--ink-2); max-width: 64ch; }

.at-clear-mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  font-size: 20px;
  font-weight: 700;
  background: var(--ok-wash);
  color: var(--ok);
  border: 1px solid var(--ok-edge);
}

/* A row that carries its own tone, for lists that mix today and this week (the home page). */
.at-edge { --at-tone: var(--review); position: relative; }
.at-edge.is-urgent { --at-tone: var(--danger); }
.at-edge::before { content: ""; position: absolute; left: 0; top: 12px; bottom: 12px; width: 3px; border-radius: 0 3px 3px 0; background: var(--at-tone); }

/* ---------- Attendance not marked: the classes behind the finding ---------- */

.um-campuses { display: flex; flex-wrap: wrap; gap: 6px; }

.um-campus {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.um-campus b { font-weight: 600; color: var(--danger); font-variant-numeric: tabular-nums; }
.um-campus:hover { border-color: var(--line-2); color: var(--ink); }
.um-campus.is-on { background: var(--accent-wash); border-color: var(--accent-edge); color: var(--ink); }
.um-campus:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: 2px; }

.um-day { margin-bottom: 14px; }
.um-old { color: var(--danger); font-weight: 500; }

.um-item { align-items: flex-start; }
.um-time { flex: none; width: 92px; padding-top: 1px; font-size: 13px; font-weight: 500; color: var(--ink); font-variant-numeric: tabular-nums; display: flex; flex-direction: column; }
.um-time .faint { font-weight: 400; font-size: 12px; }
.um-item > .btn { align-self: center; }

@media (max-width: 560px) {
  .um-time { width: auto; flex-direction: row; gap: 6px; }
  .um-item > .btn { align-self: flex-start; }
}

/* ---------- The campus home: pulse, figures, classes ---------- */

.ch-pulse { margin-bottom: 16px; }

.ch-pulse-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 20px 22px 0;
}

.ch-title { font-family: var(--display); font-size: 26px; font-weight: 500; letter-spacing: -0.025em; line-height: 1.15; }

.ch-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  margin: 18px 22px 0;
  border-top: 1px solid var(--line);
}

.ch-figure { display: flex; flex-direction: column; gap: 4px; padding: 14px 16px 14px 0; }
.ch-figure + .ch-figure { padding-left: 16px; border-left: 1px solid var(--line); }

.ch-figure-value {
  font-family: var(--display);
  font-size: 28px;
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.ch-figure-value small { font-size: 15px; letter-spacing: 0; color: var(--ink-3); }
.ch-figure-value.is-low { color: var(--danger); }
.ch-figure-label { font-size: 12.5px; color: var(--ink-2); line-height: 1.35; }

.ch-days-wrap { padding: 16px 22px 18px; background: var(--surface-2); border-top: 1px solid var(--line); }
.ch-days-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px 16px; flex-wrap: wrap; margin-bottom: 10px; }
.ch-legend { display: flex; gap: 14px; flex-wrap: wrap; }
.ch-legend span { display: inline-flex; align-items: center; gap: 6px; }

.ch-key { display: inline-block; width: 10px; height: 10px; border-radius: 2px; background: var(--accent-hi); }
.ch-key.is-unmarked { background: var(--danger); }
.ch-key.is-floor { height: 0; border-radius: 0; border-top: 2px dashed var(--ink-3); background: none; }

.ch-days { display: grid; grid-template-columns: repeat(14, minmax(0, 1fr)); gap: 6px; }
.ch-day { display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 0; }

.ch-day-plot {
  position: relative;
  width: 100%;
  height: 88px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* The 80% floor, drawn across every column at the same height so it reads as one line. */
  background: linear-gradient(to bottom, transparent calc(20% - 1px), var(--line-2) calc(20% - 1px), var(--line-2) calc(20% + 1px), transparent calc(20% + 1px));
}

.ch-day-bar { display: block; width: min(100%, 22px); min-height: 3px; background: var(--accent-hi); border-radius: 4px 4px 0 0; }
.ch-day-bar.is-low { background: var(--review); }
.ch-day-bar.is-unmarked { height: 14px; background: var(--danger); }
.ch-day-none { width: 5px; height: 5px; border-radius: 50%; background: var(--line-2); margin-bottom: 1px; }

.ch-day-label { font-size: 10.5px; line-height: 1.25; text-align: center; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.ch-day.is-today .ch-day-label { color: var(--ink); font-weight: 600; }

.ch-class-name { font-weight: 500; color: var(--ink); }

.ch-meter { display: flex; align-items: center; gap: 10px; }
.ch-meter-value { min-width: 38px; font-variant-numeric: tabular-nums; font-weight: 500; color: var(--ink); }
.ch-meter-value.is-low { color: var(--danger); }
.ch-meter-track { position: relative; flex: 1; max-width: 120px; height: 6px; border-radius: 999px; background: var(--surface-3); }
.ch-meter-fill { position: absolute; inset: 0 auto 0 0; border-radius: 999px; background: var(--ok); }
.ch-meter-fill.is-low { background: var(--danger); }
.ch-meter-fill.is-hours { background: var(--accent-hi); }
.ch-meter-floor { position: absolute; left: 80%; top: -3px; bottom: -3px; width: 2px; background: var(--ink-3); border-radius: 1px; }

.ch-flag { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 12px; font-weight: 500; background: var(--danger-wash); color: var(--danger); border: 1px solid var(--danger-edge); }

.ch-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }

.ch-links a {
  padding: 7px 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 13px;
  text-decoration: none;
}

.ch-links a:hover { color: var(--ink); border-color: var(--line-2); text-decoration: none; }

@media (max-width: 640px) {
  .ch-pulse-head { padding: 16px 16px 0; }
  .ch-title { font-size: 22px; }
  .ch-figures { margin: 14px 16px 0; grid-template-columns: 1fr 1fr; }
  .ch-figure:nth-child(odd) { padding-left: 0; border-left: 0; }
  .ch-figure:nth-child(n+3) { border-top: 1px solid var(--line); }
  .ch-days-wrap { padding: 14px 16px 16px; }
  .ch-days { gap: 3px; }
  .ch-day-plot { height: 64px; }
}

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

.at-clear.is-unknown .at-clear-mark { background: var(--review-wash); color: var(--review); border-color: var(--review-edge); }

@media (max-width: 560px) {
  .at-item { flex-direction: column; align-items: stretch; padding: 14px 16px 14px 20px; }
  .at-item > .btn { align-self: flex-start; }
  .at-group-head { padding: 12px 16px 10px 20px; }
  .at-clear { padding: 18px; }
}

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

.empty { padding: 52px 24px; text-align: center; color: var(--ink-2); }
.empty p { max-width: 46ch; margin-inline: auto; text-wrap: pretty; }

.empty-title {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.025em;
  color: var(--ink);
  margin-bottom: 6px;
  text-wrap: balance;
}

/* ---------- Skeleton loading: shows the shape of what is coming ---------- */

.loading { padding: 34px; text-align: center; color: var(--ink-3); font-size: 13px; }

.skeleton { padding: 18px; display: flex; flex-direction: column; gap: 11px; }

.sk-line {
  height: 11px;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 37%, var(--surface-2) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease-in-out infinite;
}

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

/* ---------- Timeline ---------- */

.timeline { position: relative; padding-left: 26px; }

.timeline::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 7px;
  bottom: 7px;
  width: 1.5px;
  background: linear-gradient(var(--line-2), var(--line));
}

.tl-item { position: relative; padding: 0 0 22px; }
.tl-item:last-child { padding-bottom: 2px; }

.tl-item::before {
  content: "";
  position: absolute;
  left: -26px;
  top: 5px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--surface);
  border: 2px solid var(--line-2);
  box-shadow: 0 0 0 3px var(--surface);
}

.tl-item.is-enrollment::before { border-color: var(--accent); background: var(--accent); }

.tl-when { font-family: var(--mono); font-size: 10px; color: var(--ink-3); letter-spacing: 0.05em; }
.tl-title { font-weight: 600; margin-top: 3px; font-size: 13.5px; }
.tl-detail { color: var(--ink-2); font-size: 13px; margin-top: 2px; }

/* What somebody actually wrote on a task or a problem.

   Sized like .tl-title and deliberately not weighted like it. A comment used to be the timeline's
   title, semi-bold at 600, which was fine while it was one line of plain text. Now that it carries
   the same formatting as the minutes, bold inside it has to be visible as bold - and 650 inside 600
   is not a difference anybody can see. Normal weight, so **bold** means something again. */
.fmt.tl-body { margin-top: 3px; font-size: 13.5px; color: var(--ink); }

/* ---------- Duplicate comparison — the highest-stakes screen in the app ---------- */

.compare { display: grid; grid-template-columns: 1fr minmax(210px, 260px) 1fr; align-items: stretch; }

.compare-side { padding: 20px; min-width: 0; }
.compare-side:first-child { border-right: 1px solid var(--line); }
.compare-side:last-child { border-left: 1px solid var(--line); }

.compare-mid {
  background: var(--surface-2);
  padding: 20px 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 11px;
  text-align: center;
}

.score-dial {
  width: 70px;
  height: 70px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--surface);
  border: 2px solid var(--review-edge);
  color: var(--review);
  font-family: var(--display);
  font-size: 25px;
  font-weight: 500;
  letter-spacing: -0.035em;
  line-height: 1;
}

.signal {
  width: 100%;
  padding: 8px 10px;
  border-radius: var(--radius-xs);
  background: var(--surface);
  border: 1px solid var(--line);
  font-size: 11.5px;
  text-align: left;
  color: var(--ink-2);
  line-height: 1.45;
}

.signal-name { font-size: 8.5px; letter-spacing: 0.12em; display: block; margin-bottom: 2px; }

.kv { display: grid; grid-template-columns: 92px 1fr; gap: 5px 12px; font-size: 12.5px; }
.kv dt { color: var(--ink-3); font-size: 11px; padding-top: 1px; }
.kv dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }

@media (max-width: 900px) {
  .compare { grid-template-columns: 1fr; }
  .compare-side:first-child, .compare-side:last-child {
    border: none;
    border-bottom: 1px solid var(--line);
  }
}

/* ============================================================================
   TIMETABLE — the week as a picture
   ============================================================================
   A booking is a rectangle whose height is its duration, so "two hours" is something you see
   rather than read. Colour identifies the course; every block is also labelled with the course
   name, so the colour never carries the meaning on its own. */

/* Sideways only. Left to `auto`, the browser gives the box a vertical scrollbar too — the first
   hour label overhangs the top edge by half its height — and the week opens already scrolled. */
.tt-wrap { overflow-x: auto; overflow-y: hidden; }

.tt {
  --tt-hour: 46px;      /* one hour of wall-clock time */
  --tt-gutter: 58px;    /* the time axis */
  --tt-lane: 148px;     /* one class abreast — a day fitting two gets two of these */

  display: grid;
  /* Overridden inline by the component, which knows how many classes each day runs at once and
     widens that day accordingly. This is the fallback for a day with nothing overlapping. */
  grid-template-columns: var(--tt-gutter) repeat(var(--tt-days), minmax(var(--tt-lane), 1fr));
  min-width: 680px;
}

.tt-head {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.tt-head-count {
  font-family: var(--sans);
  font-size: 10.5px;
  font-weight: 450;
  text-transform: none;
  letter-spacing: 0;
  color: var(--ink-3);
  margin-top: 2px;
}

/* The axis: labels sit on the hour line rather than between two of them. */
.tt-axis {
  position: relative;
  height: calc(var(--tt-hours) * var(--tt-hour));
  border-right: 1px solid var(--line);
}

.tt-tick {
  position: absolute;
  right: 8px;
  transform: translateY(-50%);
  font-family: var(--mono);
  font-size: 9.5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

/* The first and last hours have no room outside the grid, so they sit inside their own line. */
.tt-tick:first-child { transform: none; }
.tt-tick:last-child { transform: translateY(-100%); }

.tt-col {
  position: relative;
  height: calc(var(--tt-hours) * var(--tt-hour));
  border-right: 1px solid var(--line);
  /* Hour lines, recessive — they locate a block, they are not the subject. */
  background-image: repeating-linear-gradient(
    to bottom, var(--grid) 0 1px, transparent 1px var(--tt-hour));
}

.tt-col:last-child { border-right: 0; }

/*
  An empty hour, tappable to book a class there. Invisible until pointed at, so the grid still reads
  as the week rather than as a sheet of buttons; the classes are drawn after these and sit on top.
*/
.tt-slot {
  appearance: none;
  position: absolute;
  left: 0;
  width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: copy;
}

.tt-slot:hover,
.tt-slot:focus-visible {
  background: color-mix(in srgb, var(--accent, currentColor) 8%, transparent);
  outline: 1px dashed var(--line);
  outline-offset: -2px;
}

.tt-slot:hover::after,
.tt-slot:focus-visible::after {
  content: "+ Add class";
  position: absolute;
  top: 4px;
  left: 8px;
  font-size: 11px;
  color: var(--muted, currentColor);
}

.tt-slot:disabled { cursor: progress; }

/*
  A button, so the block can be opened by tap and by keyboard — the hover text it carries is
  unreachable on a phone, which is where the grid is mostly read. Everything below the first four
  declarations is undoing what a browser does to a button: the block has to look exactly as it did
  when it was a div.
*/
.tt-block {
  appearance: none;
  font: inherit;
  text-align: left;
  cursor: pointer;

  position: absolute;
  overflow: hidden;
  padding: 5px 7px;
  border-radius: var(--radius-xs);
  border: 1px solid var(--line);
  border-left: 3px solid var(--series);
  background: color-mix(in srgb, var(--series) 14%, var(--surface));
  /* A 2px surface gap so adjacent blocks read as two things, not one band. */
  margin: 0 2px 2px 0;
  font-size: 11.5px;
  line-height: 1.3;
  color: var(--ink);
}

/* Enough to say it answers to a press, and no more: the grid is dense, and a block that jumps or
   changes colour under the pointer costs more in flicker than it returns in affordance. */
.tt-block:hover { border-color: var(--series); }

.tt-block:focus-visible {
  outline: 2px solid var(--series);
  outline-offset: 1px;
}

/*
  The block as text, sitting above the Copy button.

  Selectable on purpose. A clipboard write can be refused — an insecure origin, a permissions
  policy — and when that happens the fallback the message offers has to actually be there.
*/
.tt-copy {
  margin: 0;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  background: var(--surface-2, var(--surface));
  color: var(--ink);
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  user-select: text;
}

.tt-block-course {
  display: flex;
  align-items: center;
  gap: 5px;
  font-weight: 600;
}

/* The name is the half that gives way — the class mark beside it is two characters and must not be
   the thing that gets clipped off a narrow block. */
.tt-block-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
  Which of a course's two classes this block is.

  Teaching is segregated, so one course at one campus in one term runs twice, and those two blocks
  are identical in name, colour and often room. Before this the grid could not be read in that
  case — the commonest case it has.

  It sits on the course line, not the meta line, because a block under about forty minutes hides
  everything below the name, and a pair of forty-minute classes is exactly when the question comes
  up. Monochrome and outlined: colour already means "which course" here, and giving it a second
  meaning would make both unreadable.
*/
.tt-gender {
  flex: none;
  padding: 0 3px;
  border: 1px solid currentColor;
  border-radius: 3px;
  font-size: 9px;
  font-weight: 650;
  line-height: 1.45;
  opacity: .75;
}

/*
  Room and teacher on the left, how full the class is on the right.

  The left half is the half that gives way — a long teacher name is worth truncating, and the two
  digits saying whether the class fits in the room are not.
*/
.tt-block-meta {
  display: flex;
  align-items: baseline;
  gap: 6px;
  color: var(--ink-2);
  font-size: 10.5px;
}

.tt-block-where {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tt-block-fill {
  flex: none;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}

/* The capacity is context for the enrolment, not a second number of equal weight. */
.tt-block-of { color: var(--ink-3); }

/* More students than the room seats. Amber, as everywhere else that means "worth a look". */
.tt-block-fill.is-review,
.tt-block-fill.is-review .tt-block-of {
  color: var(--review);
  font-weight: 600;
}
.tt-block-time { color: var(--ink-3); font-family: var(--mono); font-size: 10px; }

/* Under about 40 minutes there is only room for one line. */
.tt-block.is-short { padding: 2px 6px; }
.tt-block.is-short .tt-block-meta,
.tt-block.is-short .tt-block-time { display: none; }

.tt-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  align-items: center;
  font-size: 12px;
  color: var(--ink-2);
}

.tt-key { display: inline-flex; align-items: center; gap: 7px; }

.tt-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  background: var(--series);
  flex: none;
}

/*
  ---------- The week on a phone ----------

  The grid stays a grid. A week is two-dimensional and flattening it into a list would throw away
  the thing the picture is for — the list already exists below it, under "Class by class", and
  that is the version the 900px breakpoint turns into cards.

  What changes is the trade the widths make. On a desktop the grid tries to fit the week across
  the screen; on a phone it cannot, so it stops trying and buys legibility with the sideways scroll
  it was going to need anyway. The lane keeps close to its full width because a lane is what one
  class is written in, and two labs at the same hour are two lanes whatever the screen is.
*/

@media (max-width: 640px) {
  .tt {
    /* The axis loses what it can spare: "08:00" at 9.5px is about 30px of type. */
    --tt-gutter: 46px;
    --tt-lane: 136px;

    /* Taller, not shorter. A one-hour block carries three lines — course, room and teacher, then
       the time — and at 46px the last of them was being clipped by a couple of pixels. The extra
       height costs vertical scrolling on the axis the phone is already good at. */
    --tt-hour: 54px;

    /* A floor written for a desktop card. The columns set their own width here. */
    min-width: 0;
  }

  /* The blocks are what the page is; the day heading above them can give up the room. */
  .tt-head { padding: 7px 8px; }
  .tt-head-count { font-size: 10px; }

  /*
    A swipe lands on a day rather than halfway between two.

    Widening the columns bought legibility with sideways scrolling, and this is what stops that
    being a chore: at two lanes a day is narrower than the screen, so one flick is one day.
    Proximity rather than mandatory — a mandatory snap fights a reader who is deliberately looking
    at the seam between Tuesday and Wednesday.

    The time axis scrolls away with the rest, which is the price. It costs less than it looks like
    it should, because every block already prints its own hours.
  */
  .tt-wrap { scroll-snap-type: x proximity; }
  .tt-col { scroll-snap-align: start; }

  /* The axis is a snap point too, or the week opens already nudged past it: at rest the nearest
     stop would be Monday, and the page would scroll the hours off the screen by itself before
     anybody had touched it. */
  .tt-axis { scroll-snap-align: start; }
}

/* ============================================================================
   A STUDENT'S SEATS — one panel each, not a table
   ============================================================================
   A student holds one or two of these, occasionally three. As a table it was six columns wide and
   the campus column carried sixty characters of school name, so every row wrapped to six lines
   while five equal-weight buttons pushed the last column off the edge and put the card into
   sideways scroll. A table earns its shape by letting you read down a column and compare; nobody
   compares one row against nothing.

   So each seat is a panel: the course is the heading, because that is what the seat is; where and
   when it runs is a quiet line under it; and the actions are grouped by what they cost. */

.seats { display: flex; flex-direction: column; gap: var(--s3); padding: var(--s4); }

.seat {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  padding: var(--s3) var(--s4) var(--s3);
}

/* A seat that has ended is still part of the record and should not compete with a live one. */
.seat.is-ended { background: transparent; }

.seat-head {
  display: flex;
  align-items: baseline;
  gap: var(--s2) var(--s3);
  flex-wrap: wrap;
}

.seat-course {
  font-family: var(--display);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-right: auto;
}

.seat-where {
  margin-top: 3px;
  font-size: 12.5px;
  color: var(--ink-3);
  line-height: 1.5;
}

/* The divider earns its place: above it are changes to a seat that continues, below it are the two
   that end it. Without the rule, "Withdraw" sat flush against "Change section" at equal weight. */
.seat-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  align-items: center;
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px solid var(--line);
}

/* Pushes the ending actions to the far end, so reaching them is deliberate. */
.seat-actions .seat-end { margin-inline-start: auto; display: flex; gap: var(--s2); }

@media (max-width: 640px) {
  /* On a phone the two groups stack, and an ending action pinned to the right of its own row
     would read as belonging to whatever sat beside it. */
  .seat-actions .seat-end { margin-inline-start: 0; width: 100%; }
}

/* ============================================================================
   TEACHING PLAN — the term as a picture
   ============================================================================
   A term is a shape, and a table of forty rows hides it. The spine draws every class day of the
   term across one strip, so the questions a trainer actually opens the page with — how much is
   still empty, where the mid-term falls, whether the teaching is piled into the first fortnight —
   are answered before anybody scrolls.

   Every day is the same size. Drawing height from how many topics sit on a day was tried and is
   wrong here: the two states somebody opens this page to find — a day with nothing planned, a day
   taught and never written up — both carry nothing, so load-as-height drew exactly the days that
   need attention as the smallest marks on the strip. Uniform ticks put a run of empty days at the
   same weight as a run of full ones, which is the comparison actually being made.

   Brightness is urgency, not importance. A term already taught and written up is the largest part
   of the strip and the least actionable, so it recedes to a dim green; what is owing stays lit.
   Colour never carries a state alone — every one is written out in the key underneath, and each
   day carries its own date and state in a title. */

/* The bottom pad is room for the focused day's caret, which hangs below the strip and would
   otherwise be cut off by the sideways scroll's own clipping. */
.spine-wrap { overflow-x: auto; overflow-y: hidden; padding-bottom: 11px; }

.spine {
  display: flex;
  align-items: stretch;
  gap: 3px;
  height: 34px;
  min-width: 320px;
}

.spine-day {
  flex: 1 1 0;
  min-width: 6px;

  /* Barely rounded. At six pixels wide the standard radius turns every day into a lozenge, and a
     row of lozenges reads as a set of separate badges rather than one continuous term. */
  border-radius: 3px;
  background: var(--surface-3);

  /* A button, so it is reachable by keyboard and changes no address. The reset is the price of
     that: a bare <button> arrives with a border, padding and the platform's own appearance. */
  appearance: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  display: block;
  position: relative;
  transition: filter 0.12s var(--ease);
}

.spine-day:hover { filter: brightness(1.5); text-decoration: none; }
.spine-day:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: 2px; }

/* Past and written up. Dim on purpose: it is most of a term in progress and none of the work. */
.spine-day.is-recorded { background: var(--ok-edge); }

/* Past and nothing against it — somebody's paperwork rather than planning, and the loudest thing
   on the strip because it is the only state that cannot be fixed by planning ahead. */
.spine-day.is-missing { background: var(--review); }

/* Ahead, with topics on it. Cyan is demand throughout this console, and a plan is what the term
   has been asked to deliver. */
.spine-day.is-planned { background: var(--accent); }

/* Ahead, empty. An outlined slot rather than a filled one: a day waiting, drawn in the demand
   colour so a run of them reads as the work this page exists to prompt.

   The outline is the full accent rather than its edge tone. Drawn at edge strength the slots sat a
   shade off the card behind them and a fortnight of empty days sank into the panel — which is the
   one run on this strip that must not be easy to miss. */
.spine-day.is-owing {
  background: var(--accent-wash);
  box-shadow: inset 0 0 0 1.5px var(--accent);
}

/* Under daylight the wash is all but white, so an outlined slot on a white card disappears — the
   same failure as on the dark plane, arrived at from the other side. The fill does the work here
   and the outline only edges it. */
:root[data-theme="light"] .spine-day.is-owing,
:root[data-theme="light"] .spine-key .k-owing {
  background: var(--accent-edge);
  box-shadow: inset 0 0 0 1.5px var(--accent);
}

/* A paper is sat, not taught. Violet is capacity throughout this console, and an exam day is the
   term spending it rather than teaching. */
.spine-day.is-exam { background: var(--violet); }

.spine-day.is-cancelled {
  background: repeating-linear-gradient(
    -45deg, var(--danger-edge) 0 2px, var(--danger-wash) 2px 5px);
}

/* Where today falls. A ring in ink rather than another hue, so it reads on top of whatever state
   the day is already in without becoming a seventh colour. */
.spine-day.is-today {
  box-shadow: 0 0 0 2px var(--canvas), 0 0 0 3px var(--ink);
}

/* The day being looked at.
   Clicking a tick used to scroll the table and do nothing else, which on a forty-day term reads as
   a click that missed: the page moves, and nothing says which of forty near-identical rows was
   being aimed at. The strip and the row now agree on which day is in hand.

   Marked by shape rather than by colour, and that is not a stylistic preference. Today already
   owns a ring, and a second ring in the accent vanished against the accent-coloured days — which
   are most of the ones anybody clicks. A caret underneath sits outside the tick entirely, so it
   reads the same on every state and can coexist with today's ring on the same day. */
.spine-day.is-focused::after {
  content: "";
  position: absolute;
  top: calc(100% + 3px);
  left: 50%;
  transform: translateX(-50%);
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-bottom: 5px solid var(--ink);
}

/* The row it belongs to. A washed cell and a marker down the leading edge — the same pairing the
   week calendar uses for a picked day, so a selection reads the same wherever it is made. */
tr.plan-day.is-focused > td {
  background: var(--accent-wash);
}

tr.plan-day.is-focused > td:first-child {
  box-shadow: inset 2px 0 0 var(--accent-hi);
}

.spine-axis {
  display: flex;
  justify-content: space-between;
  margin-top: var(--s3);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--ink-3);
}

.spine-key {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  margin-top: var(--s3);
  font-size: 11.5px;
  color: var(--ink-3);
}

.spine-key span { display: inline-flex; align-items: center; gap: 6px; }

.spine-key i {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: var(--surface-3);
  flex: none;
}

.spine-key .k-recorded { background: var(--ok-edge); }
.spine-key .k-missing  { background: var(--review); }
.spine-key .k-planned  { background: var(--accent); }
.spine-key .k-owing    { background: var(--accent-wash); box-shadow: inset 0 0 0 1.5px var(--accent); }
.spine-key .k-exam     { background: var(--violet); }

.spine-key .k-cancelled {
  background: repeating-linear-gradient(
    -45deg, var(--danger-edge) 0 2px, var(--danger-wash) 2px 5px);
}

/* ---------- Topics with nowhere to go ----------
   A list rather than a chart: these are named things to place, not a magnitude to compare. The
   hours sit on the right in mono so a column of them can be read down and added up by eye. */

.owing-list { display: flex; flex-direction: column; }

.owing-row {
  display: grid;
  grid-template-columns: 2.2em 1fr auto;
  gap: var(--s3);
  align-items: baseline;
  padding: var(--s2) 0;
  border-top: 1px solid var(--line);
  font-size: 13px;
}

.owing-row:first-child { border-top: 0; }
.owing-row .owing-order { font-family: var(--mono); font-size: 11px; color: var(--ink-3); }
.owing-row .owing-hours { font-family: var(--mono); font-size: 11px; color: var(--ink-2); }

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

/* ============================================================================
   OVERLAYS
   ============================================================================ */

/* ---------- Command palette ----------
   Ctrl+K from anywhere. With 35,000 records the fastest route to a student should not be
   "navigate to a page, then find the search box" — it should be one keystroke. */

.palette-backdrop {
  position: fixed;
  inset: 0;
  background: rgb(4 6 10 / 62%);
  backdrop-filter: blur(4px);
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 12vh 20px 20px;
  z-index: 80;
  animation: fade 0.12s var(--ease);
}

.palette {
  width: min(620px, 100%);
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  animation: rise 0.16s var(--ease);
}

.palette-input {
  width: 100%;
  border: none;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  padding: 15px 18px;
  font-size: 15px;
  background: transparent;
}

.palette-input:focus { box-shadow: none; border-color: var(--line); }

.palette-list { max-height: 52vh; overflow-y: auto; padding: var(--s2); }

.palette-section {
  padding: var(--s2) var(--s3) var(--s1);
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.palette-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  width: 100%;
  text-align: left;
  padding: 8px 12px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  cursor: pointer;
}

.palette-item:hover,
.palette-item.is-active { background: var(--accent-wash); }
.palette-item.is-active { box-shadow: inset 2px 0 0 var(--accent-hi); }

.palette-item-main { min-width: 0; }
.palette-item-title { font-weight: 550; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette-item-sub { font-size: 11.5px; color: var(--ink-3); }

.palette-foot {
  display: flex;
  gap: var(--s4);
  padding: 9px 16px;
  border-top: 1px solid var(--line);
  background: var(--surface-2);
  font-size: 11px;
  color: var(--ink-3);
}

kbd {
  font-family: var(--mono);
  font-size: 10px;
  padding: 1px 5px;
  border: 1px solid var(--line-2);
  border-bottom-width: 2px;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--ink-2);
}

/* ---------- Modal ---------- */

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgb(4 6 10 / 68%);
  backdrop-filter: blur(4px);
  display: grid;
  place-items: center;
  padding: 24px;
  z-index: 60;
  animation: fade 0.16s var(--ease);
}

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

.modal {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  width: min(580px, 100%);
  max-height: 88vh;
  overflow-y: auto;
  border: 1px solid var(--line-2);
  animation: rise 0.2s var(--ease);
}

/* For a modal holding a table rather than a form — a mark sheet needs the width. */
.modal-wide { width: min(760px, 100%); }

@keyframes rise { from { opacity: 0; transform: translateY(8px) scale(0.99); } to { opacity: 1; transform: none; } }

.modal-head { padding: 20px 22px 15px; border-bottom: 1px solid var(--line); }

.modal-head h2 {
  font-family: var(--display);
  font-size: 20px;
  font-weight: 500;
  letter-spacing: -0.03em;
}

.modal-body { padding: 20px 22px; display: flex; flex-direction: column; gap: 16px; }

.modal-foot {
  padding: 14px 22px;
  border-top: 1px solid var(--line);
  display: flex;
  justify-content: flex-end;
  gap: 9px;
  position: sticky;
  bottom: 0;
  background: var(--surface);
}

/* ---------- Pager ---------- */

.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
  font-size: 12.5px;
  color: var(--ink-2);
  flex-wrap: wrap;
  background: var(--surface-2);
}

/* ---------- Drop zone ---------- */

.drop {
  border: 1.5px dashed var(--line-2);
  border-radius: var(--radius);
  padding: 42px 24px;
  text-align: center;
  background: var(--surface-2);
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}

.drop:hover { border-color: var(--accent); background: var(--accent-wash); }
.drop input[type="file"] { margin-top: 14px; max-width: 340px; margin-inline: auto; }

.drop-title {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.025em;
}

.drop-sub { color: var(--ink-2); margin-top: 5px; font-size: 13px; }

/* ============================================================================
   RESPONSIVE — the console on a phone
   ============================================================================
   Not a shrunken desk. Below 900px the navigation rail becomes a drawer and the four places staff
   move between all day are lifted onto a bar under the thumb; below 760px every data table is
   redrawn as a list of labelled cards, because a twelve-column register dragged sideways on a
   390px screen is a table nobody reads.

   Three breakpoints, each earning its place:
     1100px — the rail gives back width before the working area gets cramped
      900px — the shell changes shape: drawer, thumb bar, one-handed reach
      760px — tables stop being tables
      560px — overlays become sheets and the topbar sheds its labels
*/

/* ---------- 1800px and up: stop leaving a third of the display empty ----------
   The old ceiling was set when a wide screen meant 1080p. On a 1440p monitor it left roughly 770px
   of canvas doing nothing, and this is a console whose densest view — every field on show — is
   scrolling sideways at the same time. Lifting it once puts that width where it is actually
   wanted, and stops short of the point where a row gets too long to follow across.

   Deliberately a single step rather than a fluid width: below 1800px nothing moves at all. */

@media (min-width: 1800px) {
  :root { --measure: 1840px; }
}

/* ---------- 1100px: buy the working area some room ---------- */

@media (max-width: 1100px) {
  :root { --rail-w: 204px; }
  .page { padding-left: max(20px, env(safe-area-inset-left)); padding-right: max(20px, env(safe-area-inset-right)); }
}

/* ---------- 900px: the shell changes shape ---------- */

@media (max-width: 900px) {
  /* --- The rail becomes a drawer ---
     visibility, not just a transform: a rail parked off screen must also be out of the tab order,
     or the first Tab on every page walks into a menu nobody can see. */
  .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 90;
    width: min(304px, 84vw);
    flex: none;
    height: 100dvh;
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
    box-shadow: var(--shadow-lg);
    transform: translateX(-102%);
    visibility: hidden;
    transition: transform 0.26s var(--ease), visibility 0.26s var(--ease);
  }

  .shell.is-nav-open .sidebar { transform: none; visibility: visible; }

  .drawer-close { display: inline-flex; margin-left: auto; margin-right: -8px; }

  /* Wider rows in the drawer: this is now a thumb target, not a mouse target. */
  .nav { padding: 8px 10px 20px; }
  .nav a { min-height: var(--tap); padding: 10px 12px; font-size: 14px; margin-bottom: 2px; }
  .nav-icon { width: 18px; height: 18px; }
  .nav-section { padding: 18px 12px 6px; }

  /* --- Topbar --- */
  .icon-btn { display: inline-flex; }
  .topbar-brand { display: flex; }

  /* The hamburger is a 38px square with its own inset, so the bar needs less of one on that side. */
  .topbar-inner { gap: var(--s2); padding-left: max(10px, env(safe-area-inset-left)); }

  /* The search collapses to its own glyph. The label and the shortcut hint were both written for
     a keyboard that is not attached to this device. */
  .topbar-search {
    flex: none;
    width: 38px;
    height: 38px;
    max-width: none;
    padding: 0;
    justify-content: center;
    margin-left: auto;
    border-color: transparent;
    background: transparent;
  }

  .topbar-search-label, .topbar-search-keys { display: none; }
  .topbar-right { margin-left: 0; }

  /* --- The thumb bar --- */
  .tabbar {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    position: fixed;
    inset: auto 0 0 0;
    /* Above the topbar, below every overlay — a modal must never be sat on by the navigation. */
    z-index: 45;
    background: color-mix(in srgb, var(--rail) 92%, transparent);
    backdrop-filter: blur(14px);
    border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom);
  }

  .tabbar-item {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    min-height: 54px;
    padding: 7px 4px;
    border: 0;
    background: transparent;
    color: var(--ink-3);
    font: inherit;
    font-size: 10px;
    font-weight: 500;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: color 0.15s var(--ease);
  }

  .tabbar-item svg { width: 21px; height: 21px; flex: none; }
  .tabbar-item span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
  .tabbar-item:hover { color: var(--ink); text-decoration: none; }
  .tabbar-item:active { color: var(--accent-hi); }
  .tabbar-item.is-active { color: var(--accent-hi); }

  /* Where you are, marked on the edge the bar is attached to. */
  .tabbar-item.is-active::before {
    content: "";
    position: absolute;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 26px;
    height: 2px;
    border-radius: 0 0 2px 2px;
    background: var(--accent-hi);
  }

  /* Something is waiting in a screen this bar does not show directly. */
  .tabbar-dot {
    position: absolute;
    top: 8px;
    left: calc(50% + 7px);
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--review);
  }

  /* --- Page --- */
  .page {
    padding: 20px max(16px, env(safe-area-inset-right)) 20px max(16px, env(safe-area-inset-left));
    /* Clear of the thumb bar, so the last row of a table is never trapped under it. */
    padding-bottom: calc(78px + env(safe-area-inset-bottom));
  }

  .page-head { gap: 14px; margin-bottom: 18px; }
  .page-head > .btn, .page-head > .btn-row, .page-head > .field { margin-top: 0; width: 100%; }
  .page-head > .btn-row > .btn { flex: 1; }

  h1 { font-size: 25px; }
  .page-sub { font-size: 13px; }

  /* --- Controls sized for a finger ---
     16px is not a taste: Safari zooms the whole page when a field smaller than that takes focus,
     and a console that jumps every time somebody searches feels broken. */
  input[type="text"], input[type="search"], input[type="number"], input[type="date"],
  input[type="email"], input[type="password"], input[type="tel"], select, textarea {
    font-size: 16px;
    min-height: var(--tap);
    padding: 9px 12px;
  }

  select { padding-right: 32px; background-position: right 16px center, right 11px center; }
  textarea { min-height: 88px; }
  input[type="checkbox"], input[type="radio"] { width: 20px; height: 20px; }

  .btn { min-height: 40px; padding: 8px 16px; }
  .btn-sm { min-height: 34px; padding: 6px 12px; }

  /* Two columns holds nine filters inside one screen. One column would push the table below the
     fold on every list in the app. */
  .filters { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 11px; }

  /* --- Panels --- */
  .card-head { padding: 12px 14px; }
  .card-body { padding: 14px; }
  .viz-head { padding: 13px 14px 10px; }
  .viz-body { padding: 0 14px 14px; }
  .viz-foot { padding: 10px 14px; }
  .grid { gap: 12px; }

  /* Two tiles across rather than one — a stat card the width of a phone wastes the screen. */
  .stats { grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 10px; }
  .stat { padding: 13px 14px 14px; }
  .stat-value { font-size: 26px; }
  .viz-figure { font-size: 26px; }
  .donut-wrap { gap: 16px; }

  /* Tabs run off the side of a phone; they scroll rather than wrap into a block. */
  .tabs {
    gap: var(--s4);
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

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

  .pager { padding: 11px 14px; }
  .pager .btn-row { width: 100%; justify-content: flex-end; }

  .bench { padding: 13px 14px; }
  .bench-controls input { min-width: 100%; }
  .rate button { width: 42px; height: 42px; font-size: 14px; }

  .domain-head { padding: 11px 14px; }
  .timeline { padding-left: 22px; }
  .kv { grid-template-columns: 84px 1fr; }
  .drop { padding: 28px 16px; }
  .photos { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
  .report-head { gap: 14px; }
  .report-score { align-items: flex-start; text-align: left; }
}

/* ---------- 900px: a table becomes a list of cards ----------
   Each row is redrawn as its own labelled block. The column names come from the heading row at
   runtime (see bqTables in index.html), so a table gains this for free the day it is written and
   never drifts out of step when a column moves.

   The same breakpoint as the shell, deliberately: the moment the rail becomes a drawer, there is
   no longer room for a twelve-column register either. A portrait tablet tried to keep the columns
   and broke email addresses down the middle of a word to do it. */

@media (max-width: 900px) {
  .table-wrap { overflow-x: visible; }

  .table-wrap > table,
  .table-wrap > table > tbody { display: block; width: 100%; }

  .table-wrap > table > thead { display: none; }

  .table-wrap > table > tbody > tr {
    display: block;
    padding: 12px 14px 13px;
    border-bottom: 1px solid var(--line);
  }

  .table-wrap > table > tbody > tr:last-child { border-bottom: none; }

  /* Hover is a mouse idea, and on touch it sticks after the tap and marks the wrong row. */
  .table-wrap > table > tbody > tr:hover { background: none; }

  .table-wrap > table > tbody > tr > td {
    display: grid;
    grid-template-columns: minmax(0, 6.5rem) minmax(0, 1fr);
    gap: 3px 12px;
    align-items: baseline;
    /* Nothing stretches to the column: a status chip is the width of its own word, not of the
       space next to a label. */
    justify-items: start;
    padding: 3px 0;
    border: none;
    font-size: 13px;
    /* A right-aligned figure has no column to line up with once the column is gone. */
    text-align: left;
    white-space: normal;
    overflow-wrap: anywhere;
  }

  .table-wrap > table > tbody > tr > td::before {
    content: attr(data-label);
    grid-column: 1;
    grid-row: 1;
    font-family: var(--mono);
    font-size: 9px;
    font-weight: 500;
    letter-spacing: 0.13em;
    line-height: 1.6;
    text-transform: uppercase;
    color: var(--ink-3);
    overflow-wrap: anywhere;
  }

  /*
    A cell is rarely one value. It carries a name and, under it, the things that qualify the name —
    a registration number, an "applied" caveat, a second phone. Those are separate elements, so
    without this they auto-place into the label column and read as labels themselves. Pinning every
    child to the value column keeps the card a clean two-column read however many lines a cell has.
  */
  .table-wrap > table > tbody > tr > td > * { grid-column: 2; }

  /* A cell under a blank heading names nothing — a select box, a row action. It takes the width. */
  .table-wrap > table > tbody > tr > td:not([data-label]) { display: block; padding: 2px 0; }
  .table-wrap > table > tbody > tr > td:not([data-label]):last-child { padding-top: 10px; }
  .table-wrap > table > tbody > tr > td:not([data-label]):last-child .btn { width: 100%; }

  /* A spanning cell — "Nothing recorded", a domain sub-head — was never a record. */
  .table-wrap > table > tbody > tr > td[colspan] { display: block; padding: 4px 0; }
  .table-wrap > table > tbody > tr > td[colspan]::before { content: none; }

  /* Widths were written for a column that no longer exists. The floor under .cell-wrap in
     particular is there to stop a column being starved by its neighbours, and in a card there are
     no neighbours to be starved by. */
  .table-wrap > table > tbody > tr > td[style*="max-width"] { max-width: none !important; }
  .table-wrap > table > tbody > tr > td.cell-wrap { min-width: 0; }

  /* The printed report is a document, not a screen, so it keeps its grid and just tightens up. */
  .report { overflow-x: auto; }
  .report-table { font-size: 11.5px; }
  .report-table th, .report-table td { padding: 6px 8px; }
}

/* A tablet has room for two cards abreast. One column of label-and-value down the middle of an
   834px screen leaves half the display empty and doubles the scrolling. */

@media (min-width: 620px) and (max-width: 900px) {
  .table-wrap > table > tbody {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 12px;
    padding: 14px;
  }

  .table-wrap > table > tbody > tr,
  .table-wrap > table > tbody > tr:last-child {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
  }
}

/* ---------- 560px: overlays become sheets ---------- */

@media (max-width: 560px) {
  /* A dialog anchored to the bottom edge is inside thumb reach; one floating in the middle of the
     screen is not, and its buttons end up furthest from the hand holding the phone. */
  .modal-backdrop { padding: 0; place-items: end stretch; }

  .modal {
    width: 100%;
    max-height: 92dvh;
    border-radius: var(--radius) var(--radius) 0 0;
    border-bottom: none;
    animation: sheet 0.24s var(--ease);
  }

  .modal-head { padding: 18px 18px 14px; }
  .modal-body { padding: 18px; }

  .modal-foot {
    padding: 13px 18px calc(13px + env(safe-area-inset-bottom));
    flex-direction: row-reverse;
  }

  /* The confirming action is the wide one and sits under the thumb; row-reverse above keeps it on
     the side the hand is already on. */
  .modal-foot .btn { flex: 1; min-height: 44px; }

  /* The palette takes the whole screen: a phone keyboard covers two thirds of a floating one. */
  .palette-backdrop { padding: 0; align-items: stretch; }

  .palette {
    width: 100%;
    height: 100dvh;
    max-height: none;
    border: none;
    border-radius: 0;
    display: flex;
    flex-direction: column;
  }

  .palette-input { font-size: 16px; padding-top: calc(15px + env(safe-area-inset-top)); }
  .palette-list { flex: 1; max-height: none; }
  .palette-item { padding: 11px 12px; min-height: var(--tap); }
  .palette-foot { display: none; }

  /* The theme switch keeps its dot and drops its word — the accessible name still says both. */
  .theme-toggle {
    width: 38px;
    height: 38px;
    padding: 0;
    justify-content: center;
    font-size: 0;
    border-color: transparent;
    background: transparent;
  }

  .account-trigger { padding: 3px; border-color: transparent; }
  .account-sheet { min-width: min(280px, calc(100vw - 24px)); }

  .stats { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); }
  .grid-2, .grid-3 { grid-template-columns: 1fr; }
  .factrow { gap: 6px 14px; }
  .campus-strip { gap: 10px 20px; }
  .public-card .card-body { padding: 20px 16px; }
}

@keyframes sheet {
  from { transform: translateY(100%); }
  to { transform: none; }
}

/* ---------- The public page ----------
   Somebody answering a feedback form is on a phone once, so it is the only shape that matters. */

.public-head {
  padding-top: calc(18px + env(safe-area-inset-top));
  padding-left: max(22px, env(safe-area-inset-left));
  padding-right: max(22px, env(safe-area-inset-right));
}

.public-main {
  padding-left: max(20px, env(safe-area-inset-left));
  padding-right: max(20px, env(safe-area-inset-right));
}

.public-foot { padding-bottom: calc(32px + env(safe-area-inset-bottom)); }

@media (max-width: 560px) {
  .scale button { min-height: 56px; padding: 10px 2px; }
  .scale-value { font-size: 19px; }
  .public-title { font-size: 23px; }
}

/* ---------- Motion ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  /* The drawer still has to leave the tab order, so its visibility change cannot be instant-off
     while the transform it is tied to is cancelled. */
  .sidebar { transition: none; }
}

/* ---------- Print ----------
   The thumb bar and the drawer are screen furniture; paper gets neither. */

@media print {
  .tabbar, .scrim, .skip-link, .icon-btn, .topbar-brand { display: none !important; }
}

/* ---------- Viewing as somebody else ---------- */

/*
  Amber rather than red. A borrowed session is a state to stay aware of, not a fault — the same
  reading --review carries everywhere else in the console — and a red bar across the top of every
  screen would train people to ignore it within a morning.

  Sticky under the topbar rather than inside it: the topbar has a fixed height that the account menu
  and search already fill, and a session-wide condition should not have to compete with them for
  room. Its z-index sits just below the topbar's 40 so the account sheet still opens over it.
*/
.viewas {
  position: sticky;
  top: calc(var(--topbar-h) + env(safe-area-inset-top));
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 24px;
  font-size: 13px;
  color: var(--review);
  background: var(--review-wash);
  border-bottom: 1px solid var(--review-edge);
}

/* A filled dot, not an icon. It reads at a glance and needs no glyph set. */
.viewas-mark {
  flex: none;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--review);
}

.viewas-text { flex: 1; min-width: 0; }

.viewas-text b { font-weight: 650; }

/* Who is really here — quieter than the borrowed name, which is what the screen is showing. */
.viewas-who { color: var(--ink-2); margin-left: 6px; }

.viewas .btn { flex: none; }

/*
  On a phone the sentence and the button stop fitting side by side, and the button is the half that
  must not be pushed off the edge — it is the way out.
*/
@media (max-width: 640px) {
  .viewas { flex-wrap: wrap; padding: 10px 16px; }
  .viewas-text { flex: 1 1 100%; }
  .viewas-who { display: block; margin-left: 0; }
}

/*
  A number in a table that has gone past its limit — a class with more students enrolled than the
  room it meets in has seats.

  Amber, matching every other "worth a look" in the console, and carried on the figure itself rather
  than on the row: one cell is wrong, and colouring the whole row would implicate the time, the
  teacher and the course alongside it.
*/
td.num.is-review { color: var(--review); }

/*
  A register unfolded under the class it belongs to.

  Recessed rather than highlighted: it is detail about the row above, not a row of its own, and
  giving it the same weight as a class would make the table read as twice as many classes.
*/
tr.row-expanded > td {
  background: var(--surface-2);
  padding: 12px 14px;
  border-top: none;
}

/*
  The way a role comes off its chip.

  Inside the chip rather than beside it, because it acts on that grant and nothing else — the same
  role held at two campuses is two chips, and a button sitting between them would be ambiguous
  about which it removed.
*/
.chip-x {
  margin-left: 5px;
  padding: 0 2px;
  border: 0;
  background: none;
  color: inherit;
  opacity: .5;
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
}

.chip-x:hover:not(:disabled) { opacity: 1; color: var(--danger); }
.chip-x:disabled { cursor: default; opacity: .3; }

/* ---- Money ------------------------------------------------------------------------------
   Figures the institute reads as accounts. A shortfall is written in parentheses rather than
   with a minus sign, matching the summary sheet a Director has in their hand, and the colour
   is a second cue on top of that — never the only one, because the parentheses have to carry
   the meaning on a monochrome print and for anyone who cannot separate the two hues.        */

.money { font-variant-numeric: tabular-nums; white-space: nowrap; }

.is-negative { color: var(--danger); }
.is-positive { color: var(--ok); }

/* The row that a report is actually about — a total, a deficit — set apart from the rows that
   add up to it. */
.money-total { font-weight: 600; font-variant-numeric: tabular-nums; }

/* A totals row on a counting report — banded and set apart from the rows above, as the money
   reports' are on a phone, because it is the answer rather than another record. */
tr.totals-row > td {
  border-top: 2px solid var(--line-2);
  background: var(--surface-2);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* A stat tile holding money.

   It keeps the house display face and very nearly the house size — the finance pages should not
   read as a quieter part of the application than the ones counting students. What it changes is
   two things a rupee figure needs and a count does not: the size is fluid, because "Rs 26,761,243"
   is twice the width of "38,036" and would otherwise run out of a 178px tile; and the digits are
   tabular, so four totals sitting side by side line up rather than drifting. */
.stat-value.is-money {
  /* Fluid, and never broken across lines. Two tiles sit abreast on a 414px phone, which leaves
     about 125px of usable width — enough for "Rs 4,855,581" only if the type comes down with the
     viewport. Without the nowrap the browser takes the cheapest break it can find, which is
     straight after the "Rs", and the figure lands on its own line under a floating unit. */
  font-size: clamp(16px, 1.6vw + 11px, 30px);
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;

  /* The floor is set against the worst case the institute actually has: two tiles abreast on a
     414px phone leave about 125px of usable width, and "Rs 30,724,748" — thirteen characters, and
     wider than anything in four years of their accounts — needs roughly 115px at 16px. */
  min-width: 0;
}

/* A figure that was carried in from the old spreadsheets rather than recorded payment by
   payment. Marked wherever it appears, so a total nobody can drill into never looks like one
   that can. */
.carried {
  font-size: 11px;
  color: var(--ink-3);
  border: 1px dashed var(--line);
  border-radius: 4px;
  padding: 1px 5px;
  white-space: nowrap;
}

/* A band inside a table that names the section the rows beneath it belong to — "Operating
   expenditure", then "Assets purchased". The institute's own report is written this way, with
   each section subtotalled before the two are added, and running them together as one flat list
   would lose the distinction the totals rest on. */
.section-row td {
  background: var(--surface-2);
  border-top: 1px solid var(--line);
  font-size: 12px;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--ink-2);
}

/* ---- Receipts ---------------------------------------------------------------------------
   The paperwork attached to a payment. A thumbnail rather than a filename alone, because
   recognising the right bill is what somebody is actually doing here and a row of identical
   "IMG_4471.jpg" tells them nothing.                                                        */

.receipt-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }

.receipt {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}

.receipt-thumb {
  width: 44px;
  height: 44px;
  flex: 0 0 44px;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid var(--line);
  background: var(--surface);
  display: grid;
  place-items: center;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--ink-3);
  text-decoration: none;
}

.receipt-thumb-empty { background: var(--surface); }

.receipt-name {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

/* The filename is the one thing here that can be arbitrarily long, and it must not push the
   Remove button off the dialog. */
.receipt-name > a {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink);
}

/* ---- Totals on a narrow screen ----------------------------------------------------------

   The stacking rules above turn a table into a card per row, and they cover thead and tbody
   because those are the only parts the rest of the app uses. The finance reports are the first
   screens here that carry a <tfoot>, and a totals row is the one line on a money report nobody
   can do without — left alone it stays a real table row inside a table that is no longer one,
   and nine columns of it run straight off the side of a phone.

   So the footer gets the same treatment, and then a little more: it is the answer rather than
   another record, so it is banded and set apart from the rows above it.

   bqTables labels tbody from the headings and does not walk tfoot, so these cells carry their
   own data-label. That is deliberate — a totals row rarely lines up one-to-one with the headings
   anyway, because the caption spans several of them.                                          */

@media (max-width: 900px) {
  .table-wrap > table > tfoot { display: block; }

  .table-wrap > table > tfoot > tr {
    display: block;
    padding: 12px 14px 13px;
    background: var(--surface-2);
    border-top: 1px solid var(--line);
  }

  .table-wrap > table > tfoot > tr > td {
    display: grid;
    grid-template-columns: minmax(0, 6.5rem) minmax(0, 1fr);
    gap: 3px 12px;
    align-items: baseline;
    justify-items: start;
    padding: 3px 0;
    border: none;
    font-size: 13px;
    text-align: left;
    white-space: normal;
    overflow-wrap: anywhere;
  }

  .table-wrap > table > tfoot > tr > td::before {
    content: attr(data-label);
    grid-column: 1;
    grid-row: 1;
    font-family: var(--mono);
    font-size: 9px;
    font-weight: 500;
    letter-spacing: 0.13em;
    line-height: 1.6;
    text-transform: uppercase;
    color: var(--ink-3);
    overflow-wrap: anywhere;
  }

  .table-wrap > table > tfoot > tr > td > * { grid-column: 2; }

  /* The caption of a totals row — "TOTALS", "Payable to head office" — names nothing on its own.
     Usually it spans the columns it is summing; where it does not, .totals-caption says so. */
  .table-wrap > table > tfoot > tr > td[colspan],
  .table-wrap > table > tfoot > tr > td.totals-caption { display: block; padding: 2px 0 6px; }

  .table-wrap > table > tfoot > tr > td[colspan]::before,
  .table-wrap > table > tfoot > tr > td.totals-caption::before { content: none; }

  /* An empty cell in a totals row is spacing for a column that no longer exists. */
  .table-wrap > table > tfoot > tr > td:empty { display: none; }

  /*
    A rupee figure must not break across lines.

    .money already says nowrap, but the stacking rule above sets white-space: normal on every cell
    and outweighs a single class — so a figure wrapped as "1,455," / "000", which reads as two
    different numbers. Restated here at the same weight to win it back.
  */
  .table-wrap > table > tbody > tr > td.money,
  .table-wrap > table > tfoot > tr > td.money { white-space: nowrap; }
}

/* A tab that is a button rather than a link.
   The setup screen switches sections in place instead of navigating, and .tab was written for an
   anchor — without this the browser draws its own button chrome around every one of them. */
button.tab {
  background: none;
  border: none;
  font-family: inherit;
  cursor: pointer;
  border-radius: 0;
}

/* An icon inside a chip. .chip is already an inline-flex row with a gap, so this only has to
   size the glyph and stop it shrinking when the chip is tight. */
.chip-icon { width: 11px; height: 11px; flex: 0 0 11px; }

/*
   A change against last year.

   Direction is an arrow, not a colour. Spending more on a head than last year is not a fault —
   a rise in "Free Laptops for Students" is the programme working — and painting every increase
   red would tell the reader something the figure does not support. The arrow says which way; the
   reader decides whether that is good.
*/
.trend { display: inline-flex; align-items: baseline; gap: 4px; font-variant-numeric: tabular-nums; }
.trend-arrow { color: var(--ink-3); font-size: 11px; line-height: 1; }

/*
   A second level of tabs, under a first.

   The finance setup screen switches between five sections that all live behind one route, and it
   sits under the module's own tab bar. Two bars drawn identically read as one confused row: the
   eye cannot tell which of them it is currently inside. So this level drops the rule underneath
   it and the sliding underline, and marks the current section with a filled pill instead —
   different in kind, not just in position.
*/
.tabs-sub {
  border-bottom: none;
  gap: 4px;
  margin-top: -8px;
  margin-bottom: var(--s5);
  flex-wrap: wrap;
}

.tabs-sub .tab {
  padding: 5px 11px;
  border-radius: 999px;
  font-size: 12.5px;
}

.tabs-sub .tab.is-active {
  background: var(--surface-2);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--line);
}

.tabs-sub .tab.is-active::after { content: none; }

/* A totals row, on a screen wide enough to still be a table.

   The finance reports are the first here to use <tfoot>, so it had no styling at all and the
   totals ran straight on from the last record — the one line a money report is read for, set
   exactly like the rows it is summing. Banded and ruled off, the way the institute's own sheets
   set it. */
.table-wrap > table > tfoot > tr > td {
  background: var(--surface-2);
  border-top: 1px solid var(--line-2);
  font-weight: 600;
}

/* A subtotal inside the body — "Total operating expenditure" before the assets section starts.
   Ruled above but not banded: it closes a section rather than the table. */
.table-wrap > table > tbody > tr.subtotal-row > td {
  border-top: 1px solid var(--line-2);
  font-weight: 600;
}

/* A button that has to read as a link, because it navigates within the page rather than to a new
   one. Used where a sentence points at another section — "add one on chart of accounts" — and a
   real button would break the line it sits in. */
.linklike {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--accent-hi);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

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

/* ── The shareable timetable ─────────────────────────────────────────────────

   A sheet that leaves the building. It is a second layout rather than a variant
   of the working timetable because the two want opposite things: that one shows
   overlaps and every term at once so a clash can be found, and this one has to
   fit a page and be read by somebody who has never seen it before.

   Everything here is sized to get a week onto one screen without scrolling
   sideways, which is the whole point of it. */

.chk {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: .85rem;
  color: var(--ink-2);
  white-space: nowrap;
  cursor: pointer;
}

.chk input { margin: 0; cursor: pointer; }

/* The list view's table. The grids are drawn as SVG now, so the only thing
   styled here is the one layout that is genuinely a table — each class once,
   with its days gathered. */
.tt-share { table-layout: fixed; width: 100%; }

.tt-share thead th { text-align: left; font-size: .8rem; }

.tt-share td { vertical-align: top; }

/* Times line up under each other so a column of them can be scanned, which
   proportional digits defeat. */
.tt-share .tt-time {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

@media print {
  .tt-share th, .tt-share td { border: 1px solid #999 !important; padding: 4px 6px; }
  .tt-share { font-size: 9pt; }
  .tt-share tr { break-inside: avoid; }
}

/* The drawn sheet.
   The SVG carries its own colours rather than the theme's, because it is the
   thing that leaves the building: it gets exported to a PNG and opened by
   somebody whose machine knows nothing about this application. A sheet that
   changed colour with the reader's dark-mode setting would be a different
   sheet to every person it was sent to. */
.tt-sheet-wrap {
  padding: 16px;
  overflow-x: auto;
  background: var(--surface-2);
}

.tt-sheet {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 auto;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 1px 3px rgb(0 0 0 / 8%);
}

@media print {
  .tt-sheet-wrap { padding: 0; background: none; overflow: visible; }
  .tt-sheet { border: none; box-shadow: none; }
}

/* ---------- Work: tasks, campus problems, meetings ---------- */

/*
  The Work module is read on a phone, standing up, between two other things. Its grammar is built
  for that: one shape for every row (a date tile, a title, two short lines of facts, the person, an
  action), colour that means one thing everywhere (red is late, cyan is today, blue is under way,
  green is done), and the primary action within thumb reach. On a desk the same rows widen into a
  list; nothing is designed twice.
*/

/* The tab bar, drawn as buttons where a page switches a list in place rather than navigating. */
.tabs button.tab { background: none; border: 0; cursor: pointer; font: inherit; }

/* A count beside a tab's label: what that list is holding right now. */
.tab-count {
  display: inline-block;
  min-width: 18px;
  margin-left: 6px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 10.5px;
  line-height: 18px;
  text-align: center;
  vertical-align: 1px;
}

.tab.is-active .tab-count { background: var(--accent-wash); color: var(--accent-hi); }
.tab-count.is-hot { background: var(--review-wash); color: var(--review); border: 1px solid var(--review-edge); line-height: 16px; }

/* The kinds of problem, as large targets for a phone held in one hand. */
.work-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
}

.work-tile {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: var(--tap);
  padding: 10px 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 13.5px;
  text-align: left;
  cursor: pointer;
}

.work-tile svg { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.work-tile:hover { border-color: var(--accent); color: var(--ink); }

.work-tile.is-active {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
  font-weight: 600;
}

/* Minutes read as prose, keeping the line breaks they were typed with. */
.work-prose { white-space: pre-line; overflow-wrap: anywhere; margin: 0; }

.work-row-late td:first-child { box-shadow: inset 3px 0 0 var(--danger); }

/* ---- Toolbar: tabs on the left, the state filter on the right, one rule underneath ---- */

.wk-toolbar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px 20px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  margin-bottom: 18px;
}

.wk-toolbar .tabs { border-bottom: 0; margin-bottom: 0; flex-wrap: wrap; }

.wk-filters { margin-bottom: 12px; }
.wk-filter-note { display: flex; gap: 12px; align-items: baseline; margin-bottom: 18px; font-size: 13px; color: var(--ink-3); }

/* The row of controls under the toolbar on a phone: a Filter button with a count, and the state. */
.wk-controls { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
.wk-filter-btn { display: none; }
.wk-filter-btn .tab-count { margin-left: 4px; }

.wk-seg {
  display: inline-flex;
  padding: 3px;
  margin-bottom: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

.wk-seg-opt {
  padding: 5px 12px;
  border: 0;
  border-radius: var(--radius-xs);
  background: none;
  color: var(--ink-3);
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  transition: color 0.15s var(--ease), background 0.15s var(--ease);
}

.wk-seg-opt:hover { color: var(--ink); }
.wk-seg-opt.is-active { background: var(--surface-3); color: var(--ink); font-weight: 600; box-shadow: inset 0 0 0 1px var(--line-2); }
.wk-seg-opt:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: 1px; }

/* ---- Stat tiles: how the open work falls against today, each a jump to its group ---- */

.wk-summary {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 24px;
}

.wk-sum {
  position: relative;
  display: grid;
  gap: 2px;
  padding: 14px 16px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  color: var(--ink-2);
  text-decoration: none;
  overflow: hidden;
  transition: border-color 0.15s var(--ease), transform 0.15s var(--ease);
}

.wk-sum:hover { border-color: var(--line-2); color: var(--ink-2); text-decoration: none; transform: translateY(-1px); }

.wk-sum-label {
  font-family: var(--mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ink-3);
}

.wk-sum-count {
  font-family: var(--display);
  font-size: 34px;
  font-weight: 500;
  letter-spacing: -0.04em;
  line-height: 1.1;
  color: var(--ink);
  margin-top: 4px;
}

.wk-sum-hint { font-size: 12px; color: var(--ink-3); }

.wk-sum-bar { display: block; height: 4px; margin-top: 12px; border-radius: 2px; background: var(--surface-3); overflow: hidden; }
.wk-sum-bar i { display: block; height: 100%; border-radius: 2px; background: var(--ink-3); }

.wk-sum.is-late { border-color: var(--danger-edge); background: var(--danger-wash); }
.wk-sum.is-late .wk-sum-count, .wk-sum.is-late .wk-sum-label { color: var(--danger); }
.wk-sum.is-late .wk-sum-bar { background: var(--danger-edge); }
.wk-sum.is-late .wk-sum-bar i { background: var(--danger); }
.wk-sum.is-today { border-color: var(--accent-edge); background: var(--accent-wash); }
.wk-sum.is-today .wk-sum-count, .wk-sum.is-today .wk-sum-label { color: var(--accent-hi); }
.wk-sum.is-today .wk-sum-bar { background: var(--accent-edge); }
.wk-sum.is-today .wk-sum-bar i { background: var(--accent-hi); }
.wk-sum.is-info { border-color: var(--info-edge); background: var(--info-wash); }
.wk-sum.is-info .wk-sum-count, .wk-sum.is-info .wk-sum-label { color: var(--info); }
.wk-sum.is-review { border-color: var(--review-edge); background: var(--review-wash); }
.wk-sum.is-review .wk-sum-count, .wk-sum.is-review .wk-sum-label { color: var(--review); }
.wk-sum.is-empty .wk-sum-count { color: var(--ink-3); }

/* Late work does not sit still. One slow breath of the tile's edge — and none at all for anybody
   who asked their device for less motion. */
@keyframes wk-breathe {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--danger) 22%, transparent); }
}

.wk-sum.is-late:not(.is-empty) { animation: wk-breathe 2.6s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .wk-sum.is-late:not(.is-empty) { animation: none; }
}

@media (max-width: 760px) {
  .wk-summary {
    display: flex;
    gap: 10px;
    margin: 0 -16px 20px;
    padding: 4px 16px 8px;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
  }

  .wk-summary::-webkit-scrollbar { display: none; }
  .wk-sum { flex: 0 0 42%; min-width: 132px; scroll-snap-align: start; padding: 12px 14px 14px; }
  .wk-sum-count { font-size: 28px; }
}

/* ---- Groups ---- */

.wk-group { margin-bottom: 26px; scroll-margin-top: calc(var(--topbar-h) + 16px); }

.wk-group-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 2px 10px;
}

.wk-group-head::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.wk-group-head h2 { font-family: var(--display); font-size: 16px; font-weight: 500; letter-spacing: -0.015em; }

.wk-group-head .wk-group-count {
  min-width: 22px;
  padding: 1px 7px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 11px;
  text-align: center;
}

.wk-group.is-late .wk-group-head h2 { color: var(--danger); }
.wk-group.is-late .wk-group-count { border-color: var(--danger-edge); background: var(--danger-wash); color: var(--danger); }
.wk-group.is-today .wk-group-head h2 { color: var(--accent-hi); }
.wk-group.is-today .wk-group-count { border-color: var(--accent-edge); background: var(--accent-wash); color: var(--accent-hi); }

/* ---- Rows ---- */

.wk-list {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.wk-list.is-dim { opacity: 0.55; pointer-events: none; transition: opacity 0.2s var(--ease); }

.wk-item {
  position: relative;
  display: grid;
  grid-template-columns: 58px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 14px;
  padding: 12px 16px 12px 14px;
  border-top: 1px solid var(--line);
  transition: background 0.15s var(--ease);
}

.wk-item:first-child { border-top: 0; }
.wk-item:hover { background: var(--surface-2); }

/* A row's left edge is its state. Wide enough to be seen from across a desk. */
.wk-item::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 5px;
  background: transparent;
}

.wk-item.is-late::before { background: var(--danger); }
.wk-item.is-today::before { background: var(--accent-hi); }
.wk-item.is-progress::before { background: var(--info); }
.wk-item.is-closed::before { background: var(--ok); opacity: 0.5; }
.wk-item.is-cancelled::before { background: var(--line-2); }

/* And the row itself takes a faint wash of the same colour, so a late task is found by the eye
   before it is read. */
.wk-item.is-late { background: color-mix(in srgb, var(--danger-wash) 55%, var(--surface)); }
.wk-item.is-late:hover { background: var(--danger-wash); }
.wk-item.is-today { background: color-mix(in srgb, var(--accent-wash) 45%, var(--surface)); }
.wk-item.is-today:hover { background: var(--accent-wash); }
.wk-item.is-closed { opacity: 0.72; }
.wk-item.is-closed:hover { opacity: 1; }

/* Something new on it since this person last looked. */
.wk-item.has-new .wk-title::before {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin: 0 7px 1px 0;
  border-radius: 50%;
  background: var(--review);
  box-shadow: 0 0 0 3px var(--review-wash);
}

/* The date tile: a tear-off calendar page, filled solid when the date matters today. */
.wk-date {
  display: grid;
  justify-items: center;
  align-content: center;
  width: 58px;
  height: 58px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--ink-2);
  line-height: 1;
}

.wk-date-day {
  font-family: var(--display);
  font-size: 25px;
  font-weight: 600;
  letter-spacing: -0.04em;
  color: var(--ink);
}

.wk-date-month { font-size: 10.5px; margin-top: 4px; text-transform: uppercase; letter-spacing: 0.08em; }

.wk-date.is-late { background: var(--danger); border-color: var(--danger); color: #fff; }
.wk-date.is-late .wk-date-day { color: #fff; }
.wk-date.is-today { background: var(--accent); border-color: var(--accent); color: #fff; }
.wk-date.is-today .wk-date-day { color: #fff; }
.wk-date.is-closed { background: transparent; border-style: dashed; }
.wk-date.is-closed .wk-date-day { color: var(--ink-3); }

.wk-main { min-width: 0; }

.wk-title {
  display: block;
  color: var(--ink);
  font-weight: 600;
  font-size: 14.5px;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.wk-title:hover { color: var(--accent-hi); }
.wk-item.is-closed .wk-title { color: var(--ink-2); font-weight: 500; }
.wk-item.is-cancelled .wk-title { text-decoration: line-through; text-decoration-color: var(--ink-3); }

.wk-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  margin-top: 5px;
  color: var(--ink-3);
  font-size: 12px;
}

.wk-meta-primary { gap: 6px 8px; }
.wk-meta + .wk-meta { margin-top: 4px; }
.wk-meta:empty { display: none; }

.wk-when { color: var(--ink-2); font-weight: 500; }
.wk-when.is-late { color: var(--danger); font-weight: 700; }
.wk-when.is-today { color: var(--accent-hi); font-weight: 700; }

/* The title's link covers the whole row; anything else clickable sits above it. */
.wk-item .wk-title::after { content: ""; position: absolute; inset: 0; }
.wk-item :is(a, button):not(.wk-title) { position: relative; z-index: 1; }
.wk-item:hover .wk-title { color: var(--accent-hi); }

.wk-meta-item { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
.wk-meta-item > :not(svg) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.wk-meta-item svg {
  width: 13px;
  height: 13px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Unread replies on a task — the same wash as the nav count, so it reads as "waiting on you". */
.wk-new {
  font-size: 11px;
  font-weight: 700;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--review-wash);
  border: 1px solid var(--review-edge);
  color: var(--review);
  white-space: nowrap;
}

/* Priority: the word and four bars, filled red or amber where it asks for attention. */
.wk-prio-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 1px 8px 1px 7px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 11px;
  font-weight: 500;
  white-space: nowrap;
}

.wk-prio-tag.is-low { color: var(--ink-3); }
.wk-prio-tag.is-high { border-color: var(--review); background: var(--review); color: #1a1200; font-weight: 700; }
.wk-prio-tag.is-urgent { border-color: var(--danger); background: var(--danger); color: #fff; font-weight: 700; }

.wk-prio { display: inline-flex; align-items: flex-end; gap: 2px; height: 11px; vertical-align: -1px; }
.wk-prio i { display: block; width: 3px; border-radius: 1px; background: var(--line-2); }
.wk-prio i:nth-child(1) { height: 4px; }
.wk-prio i:nth-child(2) { height: 6px; }
.wk-prio i:nth-child(3) { height: 8px; }
.wk-prio i:nth-child(4) { height: 11px; }
.wk-prio i.is-on { background: var(--ink-2); }
.wk-prio-tag.is-high .wk-prio i { background: rgb(0 0 0 / 18%); }
.wk-prio-tag.is-high .wk-prio i.is-on { background: #1a1200; }
.wk-prio-tag.is-urgent .wk-prio i { background: rgb(255 255 255 / 30%); }
.wk-prio-tag.is-urgent .wk-prio i.is-on { background: #fff; }

/* Who holds it: initials, and the name beside them where there is room. */
.wk-person {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--ink-2);
  font-size: 12.5px;
  white-space: nowrap;
  min-width: 0;
}

.wk-person-text { display: grid; min-width: 0; }
.wk-person-name { overflow: hidden; text-overflow: ellipsis; }

.wk-avatar {
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--accent-wash);
  border: 1px solid var(--accent-edge);
  color: var(--accent-hi);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  flex: none;
}

.wk-avatar.is-disabled { background: var(--surface-3); border-color: var(--line-2); color: var(--ink-3); }
.wk-avatar.is-giver { background: var(--surface-3); border-color: var(--line-2); color: var(--ink-2); }
.wk-avatar.is-you { background: var(--violet-wash); border-color: var(--violet-edge); color: var(--violet); }

.wk-person-role { display: block; color: var(--ink-3); font-size: 11px; }

.wk-actions { display: flex; align-items: center; gap: 6px; justify-content: flex-end; }
.wk-actions:empty { display: none; }

/* State as a dot and a word. Colour is never the only carrier — the word is always there. */
.wk-state { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink-2); white-space: nowrap; }
.wk-state::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--line-2); }
.wk-state.is-progress::before { background: var(--info); }
.wk-state.is-done::before { background: var(--ok); }
.wk-state.is-late::before { background: var(--danger); }

/* A WhatsApp link, in its own green so it is never mistaken for an action inside the application. */
.wk-wa { color: #25a85a; border-color: color-mix(in srgb, #25a85a 45%, var(--line-2)); gap: 5px; }
.wk-wa:hover:not(:disabled) { background: color-mix(in srgb, #25a85a 14%, var(--surface)); border-color: #25a85a; color: #25a85a; }
.wk-wa svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linejoin: round; stroke-linecap: round; }
.wk-wa.is-icon { padding: 4px 7px; }

/* ---- Empty states ---- */

.wk-empty { display: flex; align-items: flex-start; gap: 16px; padding: 22px 22px 24px; }

.wk-empty-mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--ink-3);
  font-size: 20px;
  font-weight: 700;
}

.wk-empty.is-clear .wk-empty-mark { background: var(--ok-wash); color: var(--ok); border: 1px solid var(--ok-edge); }
.wk-empty-text .empty-title { font-size: 17px; margin-bottom: 4px; }
.wk-empty-text p { margin: 0; color: var(--ink-2); font-size: 13.5px; max-width: 52ch; }
.wk-empty-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }

/* ---- The sticky action bar on a record's page ---- */

/* Inside its card on a desk; pinned above the thumb bar on a phone, so Mark done is never a scroll away. */
.wk-actionbar { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.wk-actionbar .btn { justify-content: center; min-height: 40px; }
.wk-actionbar .btn:only-child, .wk-actionbar .btn-primary { grid-column: 1 / -1; }

/* ---- Hero: the one fact a record is held to, said large ---- */

.wk-hero {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 16px;
  align-items: center;
  padding: 18px;
  position: relative;
  overflow: hidden;
}

.wk-hero::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 5px; background: var(--line-2); }
.wk-hero .wk-date { width: 76px; height: 76px; background: var(--surface); }
.wk-hero .wk-date-day { font-size: 32px; }
.wk-hero-label { font-size: 10.5px; font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.12em; color: var(--ink-3); }
.wk-hero-when { font-family: var(--display); font-size: 22px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.15; margin-top: 3px; color: var(--ink); }
.wk-hero-sub { margin-top: 4px; color: var(--ink-3); font-size: 12.5px; }
.wk-hero-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.wk-hero.is-late { background: var(--danger-wash); border-color: var(--danger-edge); }
.wk-hero.is-late::before { background: var(--danger); }
.wk-hero.is-late .wk-hero-when { color: var(--danger); }
.wk-hero.is-today { background: var(--accent-wash); border-color: var(--accent-edge); }
.wk-hero.is-today::before { background: var(--accent-hi); }
.wk-hero.is-today .wk-hero-when { color: var(--accent-hi); }
.wk-hero.is-closed::before { background: var(--ok); }
.wk-hero.is-cancelled::before { background: var(--ink-3); }
.wk-hero.is-info { background: var(--info-wash); border-color: var(--info-edge); }
.wk-hero.is-info::before { background: var(--info); }
.wk-hero.is-stopping { background: var(--danger-wash); border-color: var(--danger-edge); }
.wk-hero.is-stopping::before { background: var(--danger); }
.wk-hero.is-stopping .wk-hero-when { color: var(--danger); }

/* ---- The task's own page ---- */

.td-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 16px;
  align-items: start;
}

.td-main, .td-side { display: grid; gap: 16px; min-width: 0; }
.td-side { position: sticky; top: calc(var(--topbar-h) + 16px); }

.td-brief .card-body { padding: 20px 22px 22px; }
.td-prose { font-size: 14.5px; line-height: 1.7; color: var(--ink); max-width: 72ch; }
.td-none { margin: 0; color: var(--ink-3); font-style: italic; }

/* How it ended — the last word on a closed task, set as its conclusion rather than a warning. */
.td-closure {
  display: flex;
  gap: 12px;
  margin-top: 20px;
  padding: 14px 16px;
  border: 1px solid var(--ok-edge);
  border-radius: var(--radius-sm);
  background: var(--ok-wash);
}

.td-closure-mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--ok);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
}

.td-closure-title { font-weight: 600; color: var(--ink); line-height: 26px; }
.td-closure-when { margin-left: 6px; color: var(--ink-3); font-weight: 400; font-size: 12.5px; }
.td-closure .work-prose { margin-top: 4px; color: var(--ink-2); }
.td-closure.is-cancelled { border-color: var(--line-2); background: var(--surface-2); }
.td-closure.is-cancelled .td-closure-mark { background: var(--ink-3); }

/* Open → In progress → Done, drawn top to bottom so each step has room for what happened. */
.td-steps { list-style: none; margin: 0; padding: 0; }

.td-step {
  position: relative;
  display: grid;
  padding: 0 0 18px 28px;
  color: var(--ink-3);
}

.td-step:last-child { padding-bottom: 0; }

.td-step::before {
  content: "";
  position: absolute;
  left: 0;
  top: 3px;
  width: 14px;
  height: 14px;
  box-sizing: border-box;
  border-radius: 50%;
  border: 2px solid var(--line-2);
  background: var(--surface);
  z-index: 1;
}

.td-step:not(:last-child)::after {
  content: "";
  position: absolute;
  left: 6px;
  top: 19px;
  bottom: 1px;
  width: 2px;
  border-radius: 1px;
  background: var(--line);
}

.td-step-name { font-size: 13.5px; font-weight: 500; }
.td-step-sub { font-size: 12px; color: var(--ink-3); margin-top: 1px; }

.td-step.is-reached .td-step-name { color: var(--ink); }
.td-step.is-reached::before { border-color: var(--accent-hi); background: var(--accent-hi); }
.td-step.is-reached:not(.is-current)::after { background: var(--accent-hi); }
.td-step.is-current .td-step-name { font-weight: 600; }
.td-step.is-current::before { box-shadow: 0 0 0 4px var(--accent-wash); }
.td-steps.is-done .td-step.is-reached::before { border-color: var(--ok); background: var(--ok); }
.td-steps.is-done .td-step.is-reached::after { background: var(--ok); }
.td-steps.is-done .td-step.is-current::before { box-shadow: 0 0 0 4px var(--ok-wash); }
.td-steps.is-cancelled .td-step:last-child::before { border-color: var(--ink-3); background: var(--ink-3); box-shadow: none; }
.td-steps.is-cancelled .td-step-name { text-decoration: line-through; text-decoration-color: var(--ink-3); }
.td-steps.is-cancelled .td-step:last-child .td-step-name { text-decoration: none; color: var(--ink-2); }

.td-actions { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line); }
.td-cancel { color: var(--danger); }
.td-cancel:hover:not(:disabled) { border-color: var(--danger-edge); background: var(--danger-wash); color: var(--danger); }

/* Who and where: label on the left, answer on the right, one hairline between each. */
.td-facts { margin: 0; padding: 4px 18px 8px; }

.td-facts > div {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 44px;
  border-top: 1px solid var(--line);
}

.td-facts > div:first-child { border-top: 0; }
.td-facts dt { color: var(--ink-3); font-size: 12.5px; flex: none; }
.td-facts dd { margin: 0; min-width: 0; text-align: right; color: var(--ink); font-size: 13px; overflow-wrap: anywhere; }
.td-facts dd.wk-person { color: var(--ink); white-space: normal; justify-content: flex-end; }
.td-facts .wk-avatar { width: 26px; height: 26px; font-size: 10.5px; }

/* ---- The thread ---- */

.wk-thread { display: grid; gap: 14px; }

.wk-msg { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 10px; align-items: start; }
.wk-msg-body { min-width: 0; }
.wk-msg-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; font-size: 12px; color: var(--ink-3); }
.wk-msg-author { font-weight: 600; color: var(--ink); font-size: 13px; }
.wk-msg-bubble {
  margin-top: 4px;
  padding: 10px 13px;
  border: 1px solid var(--line);
  border-radius: 4px 12px 12px 12px;
  background: var(--surface-2);
  font-size: 14px;
  line-height: 1.55;
}
.wk-msg.is-you .wk-msg-bubble { background: var(--accent-wash); border-color: var(--accent-edge); }
.wk-msg-bubble .tl-body { margin: 0; }
.wk-msg-mentions { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.wk-msg-tools { margin-top: 4px; font-size: 12px; }

/* Lines the system wrote sit in the gutter as a quiet divider, not as speech. */
.wk-sys { display: flex; align-items: center; gap: 10px; color: var(--ink-3); font-size: 12px; }
.wk-sys::before, .wk-sys::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.wk-sys span { white-space: pre-line; text-align: center; max-width: 80%; }

/* The people-picker list under a search box, with a keyboard cursor. */
.wk-pick { margin-top: 4px; padding: 4px; max-height: 240px; overflow-y: auto; }
.wk-pick-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 8px;
  border: 0;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.wk-pick-row:hover, .wk-pick-row.is-cursor { background: var(--surface-2); }
.wk-pick-row .faint { font-size: 11.5px; }

/* Quick choices above a date field: Today, Tomorrow, In 3 days, Next week. */
.wk-quick { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.wk-quick .btn { padding: 3px 10px; font-size: 12px; }
.wk-quick .btn.is-active { border-color: var(--accent); color: var(--accent-hi); background: var(--accent-wash); }

/* Priority as four buttons, so the choice is one tap rather than a dropdown. */
.wk-prio-pick { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.wk-prio-pick .btn { padding: 7px 4px; font-size: 12.5px; }
.wk-prio-pick .btn.is-active { border-color: var(--accent); background: var(--accent-wash); color: var(--accent-hi); font-weight: 600; }
.wk-prio-pick .btn.is-active.is-high { border-color: var(--review); background: var(--review-wash); color: var(--review); }
.wk-prio-pick .btn.is-active.is-urgent { border-color: var(--danger); background: var(--danger-wash); color: var(--danger); }

/* A field's own validation line, shown under it as soon as the answer is wrong. */
.wk-invalid { margin-top: 4px; font-size: 12px; color: var(--danger); }

/* Problems: the block counts how long it has waited rather than when it is due. */
.wk-wait .wk-date-day { font-size: 21px; }
.wk-wait .wk-date-month { text-transform: none; letter-spacing: 0; }
.wk-wait.is-stopping { background: var(--danger); border-color: var(--danger); color: #fff; }
.wk-wait.is-stopping .wk-date-day { color: #fff; }
.wk-wait.is-escalated { background: var(--info); border-color: var(--info); color: #fff; }
.wk-wait.is-escalated .wk-date-day { color: #fff; }
.wk-item.is-stopping::before { background: var(--danger); }
.wk-item.is-stopping { background: color-mix(in srgb, var(--danger-wash) 55%, var(--surface)); }
.wk-item.is-escalated::before { background: var(--info); }
.wk-item.is-escalated { background: color-mix(in srgb, var(--info-wash) 45%, var(--surface)); }
.wk-item.is-ack::before { background: var(--review); }

.wk-flag { color: var(--danger); font-weight: 600; }

/* Attendance at a meeting, as chips: solid for present, outlined for absent. */
.wk-people { display: flex; flex-wrap: wrap; gap: 6px; }
.wk-people .wk-person { gap: 6px; padding: 3px 10px 3px 3px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface-2); font-size: 12.5px; }
.wk-people .wk-person .wk-avatar { width: 24px; height: 24px; font-size: 10px; }
.wk-people .wk-person.is-absent { border-style: dashed; color: var(--ink-3); }
.wk-people .wk-person.is-absent .wk-avatar { background: transparent; border-color: var(--line-2); color: var(--ink-3); }

/* A small progress bar: "2 of 5 done". */
.wk-bar { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; color: var(--ink-3); }
.wk-bar i { display: block; width: 56px; height: 5px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
.wk-bar i b { display: block; height: 100%; background: var(--ok); border-radius: 3px; }

/* ---- Breakpoints ---- */

@media (max-width: 1080px) {
  .td-layout { grid-template-columns: minmax(0, 1fr); }
  .td-side { position: static; order: -1; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
}

@media (max-width: 900px) {
  /* The record's primary actions follow the thumb. */
  .page.has-actionbar { padding-bottom: calc(150px + env(safe-area-inset-bottom)); }

  .wk-actionbar {
    position: fixed;
    inset: auto 0 calc(60px + env(safe-area-inset-bottom)) 0;
    z-index: 44;
    margin: 0;
    padding: 10px max(12px, env(safe-area-inset-right)) 10px max(12px, env(safe-area-inset-left));
    background: color-mix(in srgb, var(--rail) 94%, transparent);
    backdrop-filter: blur(14px);
    border-top: 1px solid var(--line);
  }

  .wk-actionbar .btn { min-height: var(--tap); font-size: 14px; }
  .td-actions { margin-top: 0; padding-top: 0; border-top: 0; }

  /* Filters go behind one button on a phone; the sheet opens them. */
  .wk-filter-btn { display: inline-flex; }
  .wk-filters.is-inline { display: none; }
}

@media (max-width: 640px) {
  .wk-item {
    grid-template-columns: 54px minmax(0, 1fr);
    grid-template-areas: "date main" "side side";
    gap: 10px 12px;
    padding: 12px 14px 12px 14px;
  }

  .wk-item .wk-date { grid-area: date; width: 54px; height: 54px; align-self: start; }
  .wk-item .wk-date-day { font-size: 23px; }
  .wk-item .wk-main { grid-area: main; }
  .wk-item .wk-side { grid-area: side; display: flex; align-items: center; justify-content: space-between; gap: 10px; }
  .wk-item .wk-side:has(.wk-actions:empty) { display: none; }
  .wk-item .wk-side:has(.wk-actions:empty) + * { margin-top: 0; }
  .wk-item .wk-actions .btn { min-height: 38px; padding: 6px 14px; font-size: 13px; }
  .wk-title { font-size: 15px; }

  .wk-hero { padding: 16px; gap: 14px; }
  .wk-hero .wk-date { width: 64px; height: 64px; }
  .wk-hero .wk-date-day { font-size: 27px; }
  .wk-hero-when { font-size: 19px; }
}

@media (min-width: 641px) {
  /* On a wide screen the person and the actions are grid columns of their own. */
  .wk-item .wk-side { display: contents; }
  .wk-item .wk-side .wk-person { max-width: 190px; }
}

/* ---------- Toasts ---------- */

.toast-stack {
  position: fixed;
  z-index: 70;
  display: grid;
  gap: 8px;
  top: calc(var(--topbar-h) + 12px);
  right: 16px;
  width: min(380px, calc(100vw - 32px));
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 12px 11px 14px;
  border: 1px solid var(--line-2);
  border-left: 4px solid var(--ok);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--shadow-pop);
  color: var(--ink);
  font-size: 13.5px;
  pointer-events: auto;
  animation: rise 0.2s var(--ease);
}

.toast.is-info { border-left-color: var(--info); }
.toast.is-review { border-left-color: var(--review); }
.toast.is-conflict { border-left-color: var(--danger); }
.toast-text { flex: 1; min-width: 0; }
.toast-link { font-weight: 600; color: var(--accent-hi); white-space: nowrap; }
.toast-close { border: 0; background: none; color: var(--ink-3); font: inherit; font-size: 18px; line-height: 1; padding: 2px 4px; cursor: pointer; }
.toast-close:hover { color: var(--ink); }

@media (max-width: 900px) {
  .toast-stack {
    top: auto;
    right: 16px;
    left: 16px;
    bottom: calc(72px + env(safe-area-inset-bottom));
    width: auto;
  }
}

/* The thumb bar's count: how many things are waiting behind the Work tab. */
.tabbar-count {
  position: absolute;
  top: 5px;
  left: calc(50% + 4px);
  min-width: 17px;
  height: 17px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
  box-shadow: 0 0 0 2px var(--rail);
}

/*
  The work calendar.

  A month grid beside a reading panel. Meetings are violet and tasks carry their state's colour, the
  same late-red and today-teal as the agenda, so a glance across the month finds the trouble.

  Two things are allowed to be large: the month's name, with a sentence under it saying what the
  month holds, and the selected day's date sheet. Everything else — the grid lines, the weekday
  row, the controls — is kept quiet so the entries are what the eye lands on. On a phone the grid
  keeps only dots and the panel underneath does the reading.
*/

/* ---- Header: the month, what it holds, and the controls ---- */

.wkc-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 14px 20px;
  margin-bottom: 16px;
}

.wkc-heading { min-width: 0; }

.wkc-month-name {
  font-family: var(--display);
  font-size: 30px;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.03em;
}

.wkc-year { color: var(--ink-3); font-weight: 400; }

.wkc-summary {
  margin: 5px 0 0;
  color: var(--ink-2);
  font-size: 13.5px;
}

.wkc-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }

/* Previous, Today, Next as one joined control: they are one decision, which month to look at. */
.wkc-nav {
  display: inline-flex;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.wkc-nav-btn {
  display: inline-grid;
  place-items: center;
  min-width: 34px;
  height: 32px;
  padding: 0 10px;
  border: 0;
  border-left: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
}

.wkc-nav-btn:first-child { border-left: 0; }
.wkc-nav-btn:hover:not(:disabled) { background: var(--surface-2); }
.wkc-nav-btn:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: -2px; }
.wkc-nav-btn:disabled { color: var(--ink-3); cursor: default; }

.wkc-nav-btn svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.wkc-seg {
  display: inline-flex;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}

.wkc-seg button {
  height: 26px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--ink-3);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.wkc-seg button:hover { color: var(--ink); }
.wkc-seg button:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: -2px; }
.wkc-seg button.is-on { background: var(--surface); color: var(--ink); font-weight: 600; box-shadow: var(--shadow-card); }

/* The legend doubles as the filter. Off reads as off: the colour drains out of the key. */
.wkc-layers { display: inline-flex; gap: 6px; }

.wkc-layer {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 30px;
  padding: 0 11px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-3);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.wkc-layer:hover { border-color: var(--line-2); color: var(--ink); }
.wkc-layer:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: 1px; }
.wkc-layer.is-on { border-color: var(--line-2); background: var(--surface); color: var(--ink); box-shadow: var(--shadow-card); }

.wkc-key {
  width: 9px;
  height: 9px;
  border-radius: 3px;
  border: 1.5px solid var(--line-2);
  background: transparent;
}

.wkc-layer.is-on.is-meeting .wkc-key { border-color: var(--violet); background: var(--violet); }
.wkc-layer.is-on.is-task .wkc-key { border-color: var(--accent); background: var(--accent); }
.wkc-layer.is-on.is-activity .wkc-key { border-color: var(--ok); background: var(--ok); }

/* ---- The month grid ---- */

.wkc-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 350px;
  gap: 16px;
  align-items: start;
}

.wkc-month { padding: 0; }

.wkc-weekdays, .wkc-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }

.wkc-weekdays div {
  padding: 10px 12px 8px;
  border-bottom: 1px solid var(--line);
  color: var(--ink-3);
  font-size: 12px;
  font-weight: 500;
}

/* Sunday is the institute's day off. Its column is tinted the whole way down rather than only its
   numbers dimmed, so a deadline set on one stands out as the oddity it is. */
.wkc-weekdays div.is-sunday { background: var(--surface-2); }

.wkc-cell {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
  min-height: 104px;
  padding: 7px 7px 9px;
  border: 0;
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color 120ms ease;
}

.wkc-cell:nth-child(7n) { border-right: 0; }
.wkc-cell:nth-last-child(-n + 7) { border-bottom: 0; }
.wkc-cell.is-sunday { background: var(--surface-2); }
.wkc-cell:hover { background: var(--surface-3); }
.wkc-cell:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: -2px; z-index: 1; }

.wkc-cell.is-outside .wkc-daynum { color: var(--ink-3); opacity: 0.6; }
.wkc-cell.is-outside .wkc-events { opacity: 0.6; }

/* Selection is a wash and a hairline, not a thick frame: it marks the day being read, it is not an
   alarm. Today keeps the filled numeral, so the two never read as the same thing. */
.wkc-cell.is-selected { background: var(--accent-wash); box-shadow: inset 0 0 0 1px var(--accent-edge); }
.wkc-cell.is-selected:hover { background: var(--accent-wash); }

.wkc-daynum {
  display: inline-grid;
  place-items: center;
  align-self: flex-start;
  min-width: 26px;
  height: 26px;
  padding: 0 4px;
  border-radius: 999px;
  font-family: var(--display);
  font-size: 13.5px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.wkc-cell.is-past .wkc-daynum { color: var(--ink-2); }
.wkc-cell.is-today .wkc-daynum { background: var(--accent); color: #fff; }
.wkc-cell.is-selected .wkc-daynum { color: var(--accent-hi); font-weight: 600; }
.wkc-cell.is-today.is-selected .wkc-daynum { color: #fff; }

.wkc-events { display: flex; flex-direction: column; gap: 3px; min-width: 0; width: 100%; }

.wkc-ev {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  padding: 3px 7px 3px 6px;
  border-radius: var(--radius-xs);
  background: var(--accent-wash);
  color: var(--ink);
  font-size: 11.5px;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.wkc-ev::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

.wkc-ev b { flex: none; font-weight: 600; font-variant-numeric: tabular-nums; }

.wkc-ev.is-meeting { background: var(--violet-wash); }
.wkc-ev.is-meeting::before { background: var(--violet); }
.wkc-ev.is-meeting.is-past { opacity: 0.7; }
.wkc-ev.is-activity { background: var(--ok-wash); }
.wkc-ev.is-activity::before { background: var(--ok); }
/* Still to come: outlined rather than filled, so a plan never reads as done. */
.wkc-ev.is-activity.is-planned { background: transparent; box-shadow: inset 0 0 0 1px var(--ok-edge); }
.wkc-ev.is-task.is-high { background: var(--review-wash); }
.wkc-ev.is-task.is-high::before { background: var(--review); }
.wkc-ev.is-task.is-late { background: var(--danger-wash); color: var(--danger); font-weight: 500; }
.wkc-ev.is-task.is-late::before { background: var(--danger); }
.wkc-ev.is-task.is-done { background: transparent; color: var(--ink-3); }
.wkc-ev.is-task.is-done::before { background: var(--ok); }
.wkc-ev.is-task.is-cancelled { background: transparent; color: var(--ink-3); text-decoration: line-through; }
.wkc-ev.is-task.is-cancelled::before { background: var(--line-2); }

.wkc-more { padding-left: 7px; color: var(--ink-2); font-size: 11px; font-weight: 500; }

.wkc-dots { display: none; gap: 3px; justify-content: center; }
.wkc-dots i { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }
.wkc-dots i.is-meeting { background: var(--violet); }
.wkc-dots i.is-activity { background: var(--ok); }
.wkc-dots i.is-activity.is-planned { background: transparent; box-shadow: inset 0 0 0 1px var(--ok); }
.wkc-dots i.is-high { background: var(--review); }
.wkc-dots i.is-late { background: var(--danger); }
.wkc-dots i.is-done, .wkc-dots i.is-cancelled { background: var(--line-2); }

/* ---- The day panel ---- */

.wkc-side { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

.wkc-day, .wkc-upcoming { padding: 16px 18px; }

.wkc-day-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  padding-bottom: 14px;
}

/* A tear-off date: the month on a coloured band, the day number large beneath it. */
.wkc-sheet {
  display: grid;
  width: 58px;
  overflow: hidden;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--surface);
  text-align: center;
  line-height: 1;
}

.wkc-sheet-month {
  padding: 4px 0 3px;
  background: var(--ink-2);
  color: var(--surface);
  font-size: 11px;
  font-weight: 600;
}

.wkc-sheet-day {
  padding: 7px 0 8px;
  font-family: var(--display);
  font-size: 26px;
  font-weight: 500;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.wkc-sheet.is-today { border-color: var(--accent-edge); }
.wkc-sheet.is-today .wkc-sheet-month { background: var(--accent); color: #fff; }
.wkc-sheet.is-today .wkc-sheet-day { color: var(--accent-hi); }
.wkc-sheet.is-past .wkc-sheet-month { background: var(--line-2); color: var(--ink); }
.wkc-sheet.is-past .wkc-sheet-day { color: var(--ink-2); }

.wkc-day-title { min-width: 0; }
.wkc-day-title h2 { font-family: var(--display); font-size: 19px; font-weight: 500; line-height: 1.2; letter-spacing: -0.02em; }
.wkc-day-when { color: var(--accent-hi); font-size: 12px; font-weight: 600; }
.wkc-day-date { color: var(--ink-3); font-size: 12.5px; }

.wkc-nothing {
  padding: 18px 16px;
  border: 1px dashed var(--line-2);
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  font-size: 13px;
  text-align: center;
}

.wkc-nothing p { margin: 0; }
.wkc-nothing-link { display: inline-block; margin-top: 6px; font-size: 13px; font-weight: 500; }

.wkc-quiet { margin: 0; color: var(--ink-3); font-size: 13px; }

.wkc-meeting {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  gap: 12px;
  padding: 14px 0;
  border-top: 1px solid var(--line);
}

.wkc-time {
  display: grid;
  justify-items: center;
  align-content: start;
  padding: 7px 0;
  border: 1px solid var(--violet-edge);
  border-radius: var(--radius-sm);
  background: var(--violet-wash);
  color: var(--violet);
  line-height: 1.1;
}

.wkc-time-h { font-family: var(--display); font-size: 16px; font-weight: 500; font-variant-numeric: tabular-nums; }
.wkc-time-ampm { font-size: 10.5px; text-align: center; }

.wkc-meeting-body { min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.wkc-meeting-body .wk-meta { margin-top: 0; }
.wkc-meeting-body .small { margin: 0; }

.wkc-agenda {
  align-self: stretch;
  padding: 9px 11px;
  border-left: 2px solid var(--violet-edge);
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
  background: var(--surface-2);
  font-size: 13px;
}

.wkc-label {
  color: var(--ink-2);
  font-size: 12px;
  font-weight: 600;
}

.wkc-agenda .wkc-label { margin-bottom: 3px; }

.wkc-sub {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 14px 0 6px;
  border-top: 1px solid var(--line);
}

.wkc-count {
  color: var(--ink-3);
  font-size: 12px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.wkc-task {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
}

.wkc-task + .wkc-task { border-top: 1px solid var(--line); }

.wkc-task-mark { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); }
.wkc-task.is-high .wkc-task-mark { background: var(--review); }
.wkc-task.is-late .wkc-task-mark { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-wash); }
.wkc-task.is-done .wkc-task-mark { background: var(--ok); }
.wkc-task.is-cancelled .wkc-task-mark { background: var(--line-2); }
.wkc-task.is-done .wk-title, .wkc-task.is-cancelled .wk-title { color: var(--ink-3); font-weight: 500; }
.wkc-task.is-cancelled .wk-title { text-decoration: line-through; }
.wkc-task .wk-meta { margin-top: 2px; }

/* ---- Next 14 days ---- */

.wkc-upcoming-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}

.wkc-upcoming h2 { font-family: var(--display); font-size: 16px; font-weight: 500; letter-spacing: -0.015em; }

.wkc-up-day {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  align-items: start;
  gap: 12px;
  width: calc(100% + 16px);
  margin: 0 -8px;
  padding: 8px;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.wkc-up-day:first-of-type { border-top: 0; }
.wkc-up-day:hover { background: var(--surface-2); }
.wkc-up-day:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: -2px; }
.wkc-up-day .wk-date { width: 44px; height: 44px; }
.wkc-up-day .wk-date-day { font-size: 18px; }
.wkc-up-day .wk-date-month { font-size: 10px; margin-top: 2px; }

.wkc-up-list { display: flex; flex-direction: column; gap: 4px; min-width: 0; padding-top: 3px; }

.wkc-up-item {
  display: block;
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.wkc-up-item b { margin-right: 6px; color: var(--ink-2); font-weight: 500; font-variant-numeric: tabular-nums; }
.wkc-up-item i { display: inline-block; width: 7px; height: 7px; margin-right: 8px; border-radius: 50%; background: var(--accent); vertical-align: 1px; }
.wkc-up-item.is-meeting i { background: var(--violet); }
.wkc-up-item.is-high i { background: var(--review); }
.wkc-up-item.is-late { color: var(--danger); }
.wkc-up-item.is-late i { background: var(--danger); }

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

@media (max-width: 1100px) {
  .wkc-layout { grid-template-columns: minmax(0, 1fr); }
  .wkc-bar { align-items: flex-start; }
}

@media (max-width: 720px) {
  .wkc-month-name { font-size: 24px; }
  .wkc-controls { width: 100%; }
  .wkc-layers { margin-left: auto; }
  .wkc-weekdays div { padding: 8px 0; text-align: center; font-size: 11px; }
  .wkc-cell { min-height: 54px; align-items: center; padding: 5px 2px 7px; }
  .wkc-daynum { align-self: center; }
  .wkc-events { display: none; }
  .wkc-dots { display: flex; }
  .wkc-day, .wkc-upcoming { padding: 14px; }
}

/*
  Marked by hand.

  The state tiles are the filter. Each names what is stopping a group of students from scanning and
  the next step for it, so the tiles read left to right as a to-do list: set-up, then the machine,
  then the habit. The one tile coloured red is the group that can scan and still is not.
*/

.hm-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px 14px;
}

.hm-filters .field { margin: 0; min-width: 150px; }
.hm-filters .hm-search { flex: 1 1 220px; }

.hm-lede {
  max-width: 78ch;
  margin: 0 0 16px;
  color: var(--ink-2);
  font-size: 14px;
  line-height: 1.55;
}

.hm-lede strong { color: var(--ink); font-weight: 600; }

/* Seven groups, sized so they sit in one row on a desk and fold evenly on anything narrower. */
.hm-states { grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); gap: 10px; }
.hm-states .stat { padding: 13px 14px 14px; }
.hm-states .stat-value { font-size: 30px; }

button.stat.hm-state {
  width: 100%;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

button.stat.hm-state:hover:not(:disabled) { border-color: var(--line-2); box-shadow: var(--shadow-pop); }
button.stat.hm-state:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: 2px; }
button.stat.hm-state:disabled { cursor: default; }

.stat.hm-state.is-empty { opacity: 0.5; box-shadow: none; }
.stat.hm-state.is-empty .stat-value { color: var(--ink-3); }

.stat.hm-state.is-danger { --stat-hue: var(--danger); }
.stat.hm-state.is-danger:not(.is-empty) .stat-value { color: var(--danger); }
.stat.hm-state.is-plain { --stat-hue: var(--line-2); }

/* The chosen tile keeps its own colour and gains a frame, so it still says which group it is. */
.stat.hm-state.is-on { box-shadow: inset 0 0 0 2px var(--accent); border-color: var(--accent); }

.hm-table-title {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: var(--display);
  font-size: 16px;
  font-weight: 500;
}

.hm-table-count { color: var(--ink-3); font-family: var(--sans); font-size: 13px; font-weight: 500; }

/* How much of a student's presence was typed in, as a short bar beside the count. */
.hm-share { display: inline-flex; align-items: center; gap: 8px; }

.hm-bar {
  display: inline-block;
  width: 48px;
  height: 6px;
  border-radius: 3px;
  background: var(--surface-3);
  overflow: hidden;
}

.hm-bar i { display: block; height: 100%; border-radius: 3px; background: var(--review); }

@media (max-width: 720px) {
  .hm-filters .field { min-width: 0; flex: 1 1 140px; }
  .hm-bar { display: none; }
}

/*
  Formatted minutes.

  Meeting text carries a handful of markers the toolbar types — headings, numbered and bulleted
  points, bold. <FormattedText> turns them into these elements; spacing stays tight, since minutes
  are read as a document, not a web page.
*/

.fmt { overflow-wrap: anywhere; line-height: 1.55; }
.fmt > * { margin: 0; }
.fmt > * + * { margin-top: 0.55em; }
.fmt p { margin: 0; }
.fmt strong { font-weight: 650; color: var(--ink); }

.fmt-h {
  font-family: var(--display);
  font-size: 1.05em;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.fmt > .fmt-h:not(:first-child) { margin-top: 0.9em; }

.fmt ol, .fmt ul { margin: 0; padding-left: 1.5em; }
.fmt li + li { margin-top: 0.2em; }
.fmt li > ol, .fmt li > ul { margin-top: 0.2em; }
.fmt li::marker { color: var(--ink-3); }
.fmt ol > li::marker { font-variant-numeric: tabular-nums; }

.fmt-editor { display: flex; flex-direction: column; }

.fmt-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 4px;
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--surface-2);
}

.fmt-tools, .fmt-tabs { display: flex; gap: 2px; }

.fmt-tool, .fmt-tabs button {
  min-width: 30px;
  height: 28px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.fmt-tool:hover:not(:disabled), .fmt-tabs button:hover { background: var(--surface); color: var(--ink); }
.fmt-tool:disabled { opacity: 0.35; cursor: default; }
.fmt-tool-bold { font-weight: 700; }
.fmt-tool-heading { font-family: var(--display); font-weight: 600; }
.fmt-tabs button.is-on { background: var(--surface); color: var(--ink); font-weight: 600; box-shadow: var(--shadow-card); }

.fmt-editor textarea, .fmt-preview {
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}

.fmt-preview {
  padding: 10px 12px;
  border: 1px solid var(--line);
  background: var(--surface);
  font-size: 14px;
}

.fmt-hint {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: 5px;
  color: var(--ink-3);
  font-size: 12px;
}

.fmt-full { color: var(--danger); font-weight: 600; }

@media (max-width: 520px) {
  .fmt-bar { flex-wrap: wrap; }
  .fmt-hint > span:first-child { display: none; }
}

/* ---- Dropout report ----
   The bands are filters as well as a chart: one row each, a bar for its share of everybody
   withdrawn, and the whole row a button. */
.dr-band {
  display: grid;
  grid-template-columns: minmax(150px, 190px) 1fr 3.5em;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 6px 8px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.dr-band:hover { background: var(--surface-2); }
.dr-band.is-on { background: var(--accent-wash); border-color: var(--accent-edge); }
.dr-band-label { font-size: 13px; }
.dr-band-bar { height: 10px; border-radius: 5px; background: var(--surface-2); overflow: hidden; }
.dr-band-bar > span { display: block; height: 100%; border-radius: 5px; background: var(--accent); }
.dr-band:first-of-type .dr-band-bar > span { background: var(--ink-3); }
.dr-band-count { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
tr.is-selected > td { background: var(--accent-wash); }

@media (max-width: 560px) {
  .dr-band { grid-template-columns: 1fr 3em; }
  .dr-band-bar { grid-column: 1 / -1; order: 3; }
}

/* ---- Student portal and quizzes ---------------------------------------------------------------
   The student pages sit on the public shell (no console rail) and are read on phones first, so
   targets are large and nothing depends on hover. */

.sp-nav {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin-left: auto;
}

.sp-nav a, .sp-nav button {
  font-size: 13px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  color: var(--ink-2, var(--ink));
  text-decoration: none;
  background: none;
  border: 1px solid transparent;
  cursor: pointer;
}

.sp-nav a.active { border-color: var(--line); background: var(--surface); color: var(--ink); }

.sp-who { font-size: 13px; color: var(--ink-3); margin-bottom: 18px; }

.sp-list { display: grid; gap: 12px; }

.sp-quiz {
  display: flex;
  align-items: center;
  gap: 14px;
  justify-content: space-between;
  flex-wrap: wrap;
}

.sp-quiz-title { font-weight: 600; font-size: 15px; }
.sp-quiz-meta { font-size: 12.5px; color: var(--ink-3); margin-top: 2px; }

.qz-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.qz-progress { font-size: 13px; color: var(--ink-3); }

.qz-clock {
  font-family: var(--mono);
  font-size: 22px;
  font-variant-numeric: tabular-nums;
}

.qz-clock.is-warning { color: var(--review); }
.qz-clock.is-critical { color: var(--danger); }

.qz-rule {
  height: 4px;
  background: var(--line);
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: 22px;
}

.qz-rule-fill {
  height: 100%;
  background: var(--accent);
  transition: width 1s linear;
}

.qz-rule-fill.is-warning { background: var(--review); }
.qz-rule-fill.is-critical { background: var(--danger); }

.qz-text {
  font-size: 18px;
  line-height: 1.45;
  white-space: pre-wrap;
  margin-bottom: 20px;
}

.qz-options { display: grid; gap: 10px; }

.qz-option {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  text-align: left;
  padding: 14px 16px;
  min-height: var(--tap, 44px);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font-size: 15px;
  cursor: pointer;
}

.qz-option:hover:not(:disabled) { border-color: var(--accent-edge); }

.qz-option.is-selected {
  border-color: var(--accent);
  background: var(--accent-wash);
}

.qz-option.is-correct { border-color: var(--ok-edge); background: var(--ok-wash); }
.qz-option.is-wrong { border-color: var(--danger-edge); background: var(--danger-wash); }

.qz-letter {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 600;
}

.qz-option.is-selected .qz-letter { background: var(--accent); border-color: var(--accent); color: #04161a; }

.qz-score {
  font-family: var(--display);
  font-size: 44px;
  line-height: 1;
  letter-spacing: -0.03em;
}

.qz-review { display: grid; gap: 18px; margin-top: 22px; }
.qz-review-q { font-weight: 600; margin-bottom: 8px; white-space: pre-wrap; }

.qz-slip {
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  break-inside: avoid;
}

.qz-slips { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 10px; }

@media print {
  .qz-no-print { display: none !important; }
  .qz-slips { grid-template-columns: repeat(2, 1fr); }
}

/* ---- Student dashboard -------------------------------------------------------------------------
   Wider than the public card pages: a dashboard is read across, not down. One column on a phone,
   a main column and a side rail from tablet width up. */

.public-main.sp-wide { max-width: 1160px; }

.sp-hero {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
  padding: 22px 24px;
  margin-bottom: 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background:
    radial-gradient(120% 140% at 100% 0%, var(--accent-wash) 0%, transparent 55%),
    var(--surface);
  box-shadow: var(--shadow-card);
}

.sp-hero h1 { font-size: 28px; margin: 4px 0 8px; }

.sp-facts { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 13px; color: var(--ink-2); }
.sp-facts span { white-space: nowrap; }
.sp-facts b { color: var(--ink); font-weight: 600; }

.sp-ring { display: flex; align-items: center; gap: 14px; }
.sp-ring svg { width: 92px; height: 92px; transform: rotate(-90deg); }
.sp-ring-track { stroke: var(--line); }
.sp-ring-value { stroke: var(--accent); transition: stroke-dashoffset 0.6s var(--ease); }
.sp-ring-figure { font-family: var(--display); font-size: 26px; letter-spacing: -0.03em; line-height: 1; }
.sp-ring-caption { font-size: 12px; color: var(--ink-3); margin-top: 4px; max-width: 150px; line-height: 1.35; }

.sp-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

@media (min-width: 900px) {
  .sp-grid { grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr); }
}

.sp-col { display: grid; gap: 16px; min-width: 0; }

.sp-section-body { padding: 14px 18px 16px; }

.sp-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 18px;
  border-top: 1px solid var(--line);
}

.sp-row:first-child { border-top: 0; }
.sp-row-title { font-weight: 600; font-size: 14px; }
.sp-row-meta { font-size: 12px; color: var(--ink-3); margin-top: 2px; }

.sp-date {
  flex: none;
  width: 46px;
  text-align: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 4px 0 5px;
  line-height: 1.1;
}

.sp-date-day { font-family: var(--display); font-size: 18px; }
.sp-date-month { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-3); }

.sp-strip { display: flex; flex-wrap: wrap; gap: 4px; }

.sp-day {
  width: 22px;
  height: 22px;
  border-radius: 5px;
  display: grid;
  place-items: center;
  font-size: 10px;
  font-weight: 700;
  border: 1px solid transparent;
  cursor: default;
}

.sp-day.is-present { background: var(--ok-wash); color: var(--ok); border-color: var(--ok-edge); }
.sp-day.is-late { background: var(--info-wash); color: var(--info); border-color: var(--info-edge); }
.sp-day.is-absent { background: var(--danger-wash); color: var(--danger); border-color: var(--danger-edge); }
.sp-day.is-excused, .sp-day.is-unmarked { background: transparent; color: var(--ink-3); border-color: var(--line); }

.sp-legend { display: flex; flex-wrap: wrap; gap: 10px; font-size: 11.5px; color: var(--ink-3); margin-top: 10px; }
.sp-legend .sp-day { width: 16px; height: 16px; font-size: 9px; display: inline-grid; margin-right: 4px; vertical-align: -3px; }

.sp-bar { margin-top: 12px; }
.sp-bar:first-child { margin-top: 0; }
.sp-bar-head { display: flex; justify-content: space-between; gap: 10px; font-size: 13px; }
.sp-bar-head .muted { font-size: 12px; }
.sp-bar-track { height: 6px; border-radius: 3px; background: var(--line); margin-top: 6px; overflow: hidden; }
.sp-bar-fill { height: 100%; border-radius: 3px; background: var(--accent); }

.sp-kind {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 7px;
  margin-left: 6px;
  vertical-align: 1px;
}

.sp-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  padding: 14px 18px;
  border-top: 1px solid var(--line);
  background: linear-gradient(90deg, var(--accent-wash), transparent 70%);
}

.sp-cta:first-child { border-top: 0; }

.sp-empty { padding: 16px 18px; font-size: 13px; color: var(--ink-3); }

/* The portal header on a phone: brand and links on one line, the strapline dropped. */
@media (max-width: 560px) {
  .public-head:has(.sp-nav) { padding: 12px 14px; gap: 8px; }
  .public-head:has(.sp-nav) .brand-sub { display: none; }
  .sp-nav { gap: 0; flex-wrap: nowrap; }
  .sp-nav a, .sp-nav button { padding: 6px 7px; font-size: 12.5px; white-space: nowrap; }
  .public-head:has(.sp-nav) .brand-mark { white-space: nowrap; }
  .sp-hero { padding: 18px; }
  .sp-hero h1 { font-size: 24px; }
}

/* ---- A quiz, staff side ------------------------------------------------------------------------
   The page's one distinctive device is the answer key: questions numbered in the margin, options
   as answer-sheet bubbles, the right one filled in. Everything around it stays quiet. */

.qd-head { margin-bottom: 18px; }

.qd-title-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  flex-wrap: wrap;
}

.qd-crumbs { display: flex; gap: 8px; font-size: 13px; color: var(--ink-3); margin-bottom: 6px; }
.qd-crumbs a { color: var(--ink-2); }

.qd-title {
  font-family: var(--display);
  font-size: 34px;
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1.1;
  margin: 0;
}

.qd-for { color: var(--ink-2); font-size: 14px; margin-top: 6px; }

.qd-actions { padding-top: 22px; }

/* Where the quiz is in its life. A real sequence, so it is drawn as one. */
.qd-life {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.qd-step {
  position: relative;
  padding: 20px 16px 0 0;
  display: grid;
  gap: 2px;
  align-content: start;
}

/* The track: a line through every dot, solid up to where the quiz has got to. */
.qd-step::before {
  content: "";
  position: absolute;
  top: 5px;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--line);
}

.qd-step.is-done::before { background: var(--accent); }

.qd-step-dot {
  position: absolute;
  top: 0;
  left: 0;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--surface);
  border: 2px solid var(--line-2);
}

.qd-step.is-done .qd-step-dot { background: var(--accent); border-color: var(--accent); }

.qd-step.is-current .qd-step-dot {
  background: var(--accent-hi);
  border-color: var(--accent-hi);
  box-shadow: 0 0 0 4px var(--glow);
}

.qd-step-name { font-weight: 600; font-size: 14px; color: var(--ink-3); }
.qd-step.is-done .qd-step-name { color: var(--ink-2); }
.qd-step.is-current .qd-step-name { color: var(--ink); }
.qd-step-caption { font-size: 12.5px; color: var(--ink-3); line-height: 1.4; max-width: 32ch; }
.qd-step.is-current .qd-step-caption { color: var(--ink-2); }

/* The settings, as facts rather than a sentence. */
.qd-facts {
  display: flex;
  flex-wrap: wrap;
  margin: 22px 0 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

.qd-facts > div {
  flex: 1 1 150px;
  padding: 12px 16px;
  border-left: 1px solid var(--line);
  min-width: 0;
}

.qd-facts > div:first-child { border-left: 0; }
.qd-facts dt { font-size: 12px; color: var(--ink-3); }
.qd-facts dd { margin: 3px 0 0; font-family: var(--display); font-size: 17px; letter-spacing: -0.01em; }
.qd-facts dd span { font-family: var(--sans); font-size: 12.5px; color: var(--ink-3); letter-spacing: 0; }

.qd-instructions {
  margin: -4px 0 16px;
  padding: 12px 16px;
  border-left: 3px solid var(--accent);
  background: var(--surface);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: 14px;
  white-space: pre-wrap;
}

.qd-count {
  display: inline-block;
  min-width: 20px;
  padding: 0 6px;
  margin-left: 4px;
  border-radius: 999px;
  background: var(--surface-3);
  font-size: 11.5px;
  text-align: center;
}

.qd-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.qd-import {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 18px;
  padding: 18px;
  margin-bottom: 14px;
}

.qd-import-input { display: grid; gap: 8px; align-content: start; }
.qd-import-input textarea { width: 100%; font-family: var(--mono); font-size: 13px; }

.qd-import-help {
  font-size: 12.5px;
  color: var(--ink-2);
  border-left: 1px solid var(--line);
  padding-left: 18px;
}

.qd-import-help pre {
  font-family: var(--mono);
  font-size: 12px;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin: 0 0 10px;
  color: var(--ink);
}

.qd-import-help p { margin: 0 0 6px; }

/* ---- The answer key ---- */

.qd-key { list-style: none; margin: 0; padding: 0; }

.qd-q {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) auto;
  gap: 4px 14px;
  padding: 18px 18px 18px 14px;
  border-top: 1px solid var(--line);
}

.qd-q:first-child { border-top: 0; }

.qd-q-num {
  font-family: var(--display);
  font-size: 22px;
  line-height: 1.15;
  color: var(--ink-3);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.qd-q-text { font-size: 15.5px; line-height: 1.45; white-space: pre-wrap; margin-bottom: 10px; max-width: 75ch; }

.qd-options {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 8px 18px;
}

.qd-option { display: flex; align-items: flex-start; gap: 10px; font-size: 14px; color: var(--ink-2); line-height: 1.4; }
.qd-option.is-correct { color: var(--ink); font-weight: 600; }

.qd-bubble {
  flex: none;
  width: 22px;
  height: 22px;
  margin-top: -1px;
  border-radius: 50%;
  border: 1.5px solid var(--line-2);
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-3);
}

/* Filled in, as on a marking key. Green is the status colour for right; the filled bubble and the
   bold text carry it for anybody who cannot see the colour. */
.qd-option.is-correct .qd-bubble { background: var(--ok); border-color: var(--ok); color: #fff; }

.qd-q-tools { display: flex; gap: 6px; align-items: flex-start; }
.qd-icon { width: 30px; padding-left: 0; padding-right: 0; justify-content: center; }

/* ---- Results ---- */

.qd-results { display: grid; gap: 16px; }

.qd-summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr);
  gap: 28px;
  padding: 20px 22px;
  align-items: end;
}

.qd-summary-figures { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 24px; }

.qd-big { font-family: var(--display); font-size: 32px; letter-spacing: -0.03em; line-height: 1; }
.qd-big span { font-size: 18px; color: var(--ink-3); }

.qd-dist { margin: 0; padding-bottom: 20px; }

.qd-dist-bars {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 2px;
  height: 140px;
  margin-top: 10px;
  border-bottom: 1px solid var(--line-2);
}

.qd-dist-col {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  position: relative;
  min-width: 0;
  height: 100%;
}

.qd-dist-col:hover .qd-dist-bar { background: var(--accent-hi); }
.qd-dist-n { font-size: 11px; color: var(--ink-2); min-height: 14px; margin-bottom: 2px; }
.qd-dist-bar { width: min(100%, 28px); background: var(--accent); border-radius: 4px 4px 0 0; }
.qd-dist-x { position: absolute; bottom: -20px; font-size: 11px; color: var(--ink-3); }

.qd-meter { display: flex; align-items: center; gap: 10px; }
.qd-meter-track { flex: 1; min-width: 70px; height: 6px; border-radius: 3px; background: var(--line); overflow: hidden; }
.qd-meter-fill { display: block; height: 100%; background: var(--accent); border-radius: 3px; }

@media (max-width: 860px) {
  .qd-summary { grid-template-columns: 1fr; }
  .qd-import { grid-template-columns: 1fr; }
  .qd-import-help { border-left: 0; padding-left: 0; }
}

@media (max-width: 640px) {
  .qd-title { font-size: 28px; }
  .qd-q { grid-template-columns: 30px minmax(0, 1fr); }
  .qd-q-tools { grid-column: 2; }
  .qd-life { grid-template-columns: 1fr; gap: 12px; }
  .qd-step::before { display: none; }
  .qd-step { padding: 0 0 0 22px; }
  .qd-actions { padding-top: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .qd-step.is-current .qd-step-dot { box-shadow: none; }
}

/* Buttons at the end of a table row, kept on one line and to the right. */
td.row-actions { white-space: nowrap; text-align: right; }
td.row-actions .btn + .btn { margin-left: 6px; }

.qd-summary { align-items: center; }

/* A quiet statement of a normal state, not a warning. */
.qd-locked {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--ink-3);
}

.qd-locked svg { width: 14px; height: 14px; flex: none; }

/* The settings strip on a phone: a tidy two-column grid instead of a ragged wrap. */
@media (max-width: 640px) {
  .qd-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .qd-facts > div { border-left: 0; border-top: 1px solid var(--line); }
  .qd-facts > div:nth-child(-n + 2) { border-top: 0; }
  .qd-facts > div:nth-child(even) { border-left: 1px solid var(--line); }
  .qd-facts > div:last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* ---- Time field (Shared/TimeField.razor) -------------------------------------------------------
   A typed field with a list of times under it, as a calendar does it. The list floats over what is
   below, so opening it never pushes a dialog's buttons out of reach. */

.tf { position: relative; min-width: 0; }

.tf-input { padding-left: 32px !important; font-variant-numeric: tabular-nums; }

.tf-icon {
  position: absolute;
  left: 10px;
  top: 50%;
  width: 15px;
  height: 15px;
  transform: translateY(-50%);
  color: var(--ink-3);
  pointer-events: none;
}

.tf.is-open .tf-icon,
.tf-input:focus + .tf-icon { color: var(--accent-hi); }

.tf.is-invalid .tf-input { border-color: var(--danger); box-shadow: 0 0 0 3px var(--danger-wash); }
.tf.is-invalid .tf-icon { color: var(--danger); }

.tf-list {
  position: absolute;
  z-index: 60;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  width: max-content;
  max-height: 248px;
  overflow-y: auto;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-pop);
  overscroll-behavior: contain;
}

.tf-option {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  padding: 6px 10px;
  border-radius: 4px;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  white-space: nowrap;
}

.tf-option.is-active { background: var(--accent-wash); color: var(--ink); }
.tf-option.is-selected { font-weight: 600; color: var(--accent-hi); }
.tf-duration { color: var(--ink-3); font-weight: 400; }

/* The touch-screen version is the browser's own picker; it still wears the console's field. */
input[type="time"], .tf-native {
  padding: 7px 11px;
  border: 1px solid var(--line-2);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  min-width: 0;
  width: 100%;
  color-scheme: inherit;
}

/* A date beside a time, for the moments that need both. */
.dt-pair { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 8px; }

/* ---- Student sign-ins: one temporary password per section ---- */

.sl-password {
  display: grid;
  grid-template-columns: auto minmax(180px, 280px) minmax(0, 1fr);
  gap: 6px 14px;
  align-items: center;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
}

@media (max-width: 760px) { .sl-password { grid-template-columns: 1fr; } }

.sl-password input { font-family: var(--mono); }

.sl-shared {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  padding: 14px 16px;
  margin-bottom: 14px;
  border: 1px solid var(--accent-edge);
  border-radius: var(--radius-sm);
  background: var(--accent-wash);
}

.sl-shared-pw { font-size: 24px; letter-spacing: 0.04em; color: var(--ink); margin-top: 2px; }

/* ---------- Work: pieces added with the phone-first pass ---------- */

/* A picture that opens in place. */
.photo-open { display: block; width: 100%; padding: 0; border: 0; background: none; cursor: zoom-in; }
.photo-open img { display: block; width: 100%; }

.wk-lightbox {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: 16px;
  background: rgb(4 6 10 / 92%);
  cursor: zoom-out;
  animation: fade 0.16s var(--ease);
}

.wk-lightbox img { max-width: 100%; max-height: calc(100vh - 96px); object-fit: contain; border-radius: var(--radius-sm); }

.wk-lightbox-bar {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--rail) 90%, transparent);
  color: var(--ink-2);
  font-size: 12.5px;
  cursor: default;
}

.wk-lightbox-bar span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The actions a record's page offers beyond the first two: a row on a desk, a sheet on a phone. */
.wk-actions-rest { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }

@media (max-width: 900px) {
  .wk-actions-rest { display: none; }
}

@media (min-width: 901px) {
  /* On a desk every action fits; the "More…" button is the phone's. */
  .wk-actionbar > .btn:last-child:not(.btn-primary):not(.td-cancel):not(:only-child):is([class*="btn"]) { }
}

/* A folding section of the minutes. */
.wk-fold { border-top: 1px solid var(--line); padding: 10px 0 4px; }
.wk-fold:first-child { border-top: 0; padding-top: 0; }
.wk-fold > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--display);
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
}
.wk-fold > summary::-webkit-details-marker { display: none; }
.wk-fold > summary::before { content: ""; width: 7px; height: 7px; border-right: 1.5px solid var(--ink-3); border-bottom: 1.5px solid var(--ink-3); transform: rotate(-45deg); transition: transform 0.15s var(--ease); }
.wk-fold[open] > summary::before { transform: rotate(45deg); }

/* The urgent tick on the report form, framed so it is not mistaken for the other. */
.chk.is-urgent { color: var(--danger); }
.chk > span { display: inline-block; }

/* A time tile on the activity log, where the row is a time rather than a date. */
.wk-date.wk-time .wk-date-day { font-size: 15px; font-family: var(--mono); letter-spacing: 0; }
.wk-date.wk-time .wk-date-month { text-transform: none; letter-spacing: 0; }

/* ---------- Assessments: the mark sheet and the papers list ---------- */

/* The sheet. One student a row, the number box large enough for a thumb, saving as you go. */
.ms { width: min(720px, 100%); }
.ms-head { padding-bottom: 14px; }
.ms-title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.ms-title-row h2 { overflow-wrap: anywhere; }

.ms-progress { display: grid; justify-items: end; gap: 4px; flex: none; min-width: 120px; }
.ms-progress-num { font-family: var(--display); font-size: 24px; font-weight: 600; letter-spacing: -0.03em; line-height: 1; color: var(--ink); }
.ms-progress-num span { font-size: 14px; color: var(--ink-3); font-weight: 500; }
.ms-progress-bar { display: block; width: 120px; height: 5px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
.ms-progress-bar i { display: block; height: 100%; background: var(--ok); border-radius: 3px; transition: width 0.25s var(--ease); }
.ms-progress-hint { font-size: 11px; color: var(--ink-3); }

.ms-body { gap: 12px; }
.ms-held { display: grid; gap: 6px; padding: 12px 14px; border: 1px solid var(--review-edge); border-radius: var(--radius-sm); background: var(--review-wash); }
.ms-held .field { max-width: 220px; }

.ms-rows { display: grid; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--surface); }

.ms-row {
  position: relative;
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px 12px;
  padding: 10px 12px 10px 10px;
  border-top: 1px solid var(--line);
}
.ms-row:first-child { border-top: 0; }
.ms-row::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 4px; background: transparent; }
.ms-row.is-marked::before { background: var(--ok); }
.ms-row.is-absent::before { background: var(--review); }
.ms-row.has-error::before { background: var(--danger); }
.ms-row.is-absent .ms-name { color: var(--ink-2); }

.ms-who { min-width: 0; }
.ms-name { font-weight: 600; font-size: 14px; color: var(--ink); overflow-wrap: anywhere; }
.ms-reg { font-size: 11px; color: var(--ink-3); }
.ms-value { font-size: 15px; white-space: nowrap; }

.ms-entry { display: flex; align-items: center; gap: 6px; }
.ms-score {
  width: 76px;
  padding: 8px 10px;
  font-family: var(--display);
  font-size: 18px;
  font-weight: 600;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.ms-score:disabled { opacity: 0.5; }
.ms-max { font-size: 12px; color: var(--ink-3); white-space: nowrap; }

.ms-abs {
  min-height: 36px;
  padding: 0 10px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-3);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.ms-abs.is-on { border-color: var(--review); background: var(--review-wash); color: var(--review); font-weight: 600; }

.ms-note-btn {
  display: grid;
  place-items: center;
  width: 32px;
  height: 36px;
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
  background: none;
  color: var(--ink-3);
  cursor: pointer;
}
.ms-note-btn svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linejoin: round; }
.ms-note-btn:hover, .ms-note-btn.is-on { color: var(--accent-hi); }
.ms-note { grid-column: 2 / -1; }
.ms-note input { width: 100%; font-size: 13px; }

.ms-state { width: 10px; display: inline-grid; place-items: center; }
.ms-dot { display: block; width: 8px; height: 8px; border-radius: 50%; }
.ms-dot.is-saved { background: var(--ok); }
.ms-dot.is-error { background: var(--danger); }
.ms-dot.is-saving { background: var(--ink-3); animation: ms-pulse 0.9s ease-in-out infinite; }
@keyframes ms-pulse { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }

.ms-returned { padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface-2); }

@media (max-width: 560px) {
  .ms-title-row { flex-direction: column; gap: 10px; }
  .ms-progress { justify-items: start; }
  .ms-progress-bar { width: 100%; }
  .ms-row { grid-template-columns: 30px minmax(0, 1fr); }
  .ms-entry { grid-column: 1 / -1; justify-content: flex-start; }
  .ms-score { width: 88px; font-size: 20px; min-height: var(--tap); }
  .ms-abs { min-height: var(--tap); }
  .ms-note { grid-column: 1 / -1; }
}

/* The paper tile: a date, filled red when past and not held. */
.wk-date.is-await { background: var(--review); border-color: var(--review); color: #1a1200; }
.wk-date.is-await .wk-date-day { color: #1a1200; }
.wk-item.is-await::before { background: var(--review); }
.wk-item.is-await { background: color-mix(in srgb, var(--review-wash) 50%, var(--surface)); }
.wk-item.is-unreturned::before { background: var(--info); }

/* A student's average on a paper, as a small meter beside the number. */
.as-mean { display: inline-flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; }
.as-mean i { display: block; width: 44px; height: 5px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
.as-mean i b { display: block; height: 100%; border-radius: 3px; background: var(--accent); }

/* The batch picker: four selects on a desk, a sheet on a phone. */
.as-picker-summary { display: none; }

@media (max-width: 900px) {
  .as-picker-inline { display: none; }
  .as-picker-summary { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
  .as-picker-summary .as-batch { min-width: 0; }
  .as-batch-title { font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .as-batch-sub { font-size: 12px; color: var(--ink-3); }
}

.nav-count.is-late { background: var(--danger-wash); border-color: var(--danger-edge); color: var(--danger); }

.wk-sum.is-ok { border-color: var(--ok-edge); background: var(--ok-wash); }
.wk-sum.is-ok .wk-sum-count, .wk-sum.is-ok .wk-sum-label { color: var(--ok); }

/* ---------- Reports: the list of reports, the shared term picker, colour discipline ---------- */

.rh-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.rh-card { display: grid; gap: 6px; padding: 16px 18px; color: var(--ink); text-decoration: none; transition: border-color 0.15s var(--ease), transform 0.15s var(--ease); }
.rh-card:hover { border-color: var(--accent); text-decoration: none; transform: translateY(-1px); }
.rh-title { font-family: var(--display); font-size: 16px; font-weight: 500; letter-spacing: -0.015em; }
.rh-q { font-size: 13.5px; color: var(--ink-2); line-height: 1.5; }
.rh-foot { font-size: 11.5px; color: var(--ink-3); font-family: var(--mono); margin-top: 4px; }

/* The term picker under the tabs, one place on every report. */
.rp-term { display: flex; justify-content: flex-end; margin: -8px 0 14px; }
.rp-term .field { min-width: 220px; }
@media (max-width: 640px) { .rp-term { justify-content: stretch; } .rp-term .field { flex: 1; } }

/* Cyan is demand and violet is capacity, everywhere (see the series comment above). The nested
   bars draw the wide bar violet by default, which is right when the wide one is the capacity;
   where the wide bar is the demand, this flips them. */
.nest-flip .nest-wide { background: var(--series-1); }
.nest-flip .nest-narrow { background: var(--series-2); }
