/*
 * Classroom Control — teacher dashboard.
 *
 * The visual language is Material 3 with a violet primary, matching the
 * classroom tools staff in this authority already use. That is a deliberate
 * choice rather than a copy: a teacher moving between two tools in one lesson
 * should not have to relearn where things live, and matching the convention
 * buys more usability than any amount of originality would.
 *
 * Three rules hold the rest together:
 *
 *   1. Colour carries meaning, never decoration. Red means a pupil needs you.
 *      A pupil the teacher locked on purpose is not a problem and is drawn in
 *      a calm amber. Nothing else is allowed to be red.
 *   2. Every foreground/background pair here has been measured against its own
 *      background for WCAG AA. tools/check.mjs re-measures them; if you change
 *      a background token, expect to change its ink token too.
 *   3. The tool is read at three metres, at a glance, by someone mid-lesson.
 *      Where density and prettiness conflict, density wins.
 */

:root {
  /* Not "light dark": that lets the browser paint native checkboxes and
     scrollbars from the OS preference even when the teacher has explicitly
     chosen light here, which turns every checkbox into a black square. Each
     theme block below sets this to match the palette it is declaring. */
  color-scheme: light;

  /* Surfaces. The page, the rail and the top bar are all one tone; cards are
     the only white thing, so a card reads as an object sitting on a ground. */
  --canvas:       #f0efff;
  --surface:      #ffffff;
  --surface-2:    #f8f7ff;   /* inputs, and panels nested inside a card */
  --control:      #dcdaf3;   /* icon buttons, chips, pills */
  --control-hi:   #cbc7ea;
  --line:         #ded8ea;
  --line-soft:    #ebe7f5;

  --primary:      #65558f;
  --primary-ink:  #ffffff;
  --primary-cont: #e9ddff;
  --on-primary-cont: #21005d;

  --ink:          #1a2e45;   /* headings, pupil names */
  --ink-2:        #453f52;   /* body */
  --ink-3:        #5f5a6b;   /* metadata. Measured on both --surface and
                                --canvas; do not lighten without re-measuring. */

  /* Red is reserved for "a pupil needs you". Everything else, including states
     the teacher created deliberately, uses a calmer tone. */
  --danger:       #b3261e;
  --danger-cont:  #ffdad6;
  --danger-edge:  #dc362e;
  /* The tone that sits ON a solid --danger / --hold / --ok fill. These flip
     with the theme, because in dark mode those three tokens become LIGHT and
     white-on-light is exactly the contrast failure this pair prevents. */
  --on-danger:    #ffffff;
  --on-hold:      #ffffff;
  --on-ok:        #ffffff;

  --hold:         #7a4600;   /* locked — the teacher did this on purpose */
  --hold-cont:    #ffddb3;
  --watch:        #533b96;   /* being viewed */
  --watch-cont:   #e8ddff;
  --ok:           #1f5c3a;
  --ok-cont:      #c8efd8;

  --shadow-1: 0 1px 2px rgba(26, 24, 40, .07), 0 1px 3px rgba(26, 24, 40, .05);
  --shadow-2: 0 4px 10px rgba(26, 24, 40, .10), 0 1px 3px rgba(26, 24, 40, .06);
  --shadow-3: 0 12px 32px rgba(26, 24, 40, .22);

  --r-card: 12px;
  --r-ctl: 10px;
  --r-pill: 999px;

  /* In light this matches the canvas, as the tools we are following do — the
     white cards carry the structure. In dark, canvas and rail at the same
     tone leave the rail as a black void with text floating in it, so it
     gets a tone of its own. */
  --rail-bg:      #f0efff;

  --rail: 258px;
  --card-w: 250px;           /* the size control rewrites this */
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* The teacher's explicit choice must win over the system preference in both
   directions, which is why the dark tokens are declared twice. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --rail-bg: #1a181f;
    --canvas: #141218; --surface: #1d1b20; --surface-2: #24222a;
    --control: #322f3a; --control-hi: #423e4b;
    --line: #49454f; --line-soft: #2b2831;
    --primary: #cfbcff; --primary-ink: #381e72;
    --primary-cont: #4f378a; --on-primary-cont: #e9ddff;
    --ink: #e7e1ec; --ink-2: #cac4d0; --ink-3: #aaa3b5;
    --danger: #ffb4ab; --danger-cont: #601410; --danger-edge: #ff8a80;
    --on-danger: #601410; --on-hold: #3a1f00; --on-ok: #0b2a15;
    --hold: #ffb95c; --hold-cont: #4a2800;
    --watch: #cfbcff; --watch-cont: #3c2a68;
    --ok: #7fdba2; --ok-cont: #17351f;
    --shadow-1: 0 1px 2px rgba(0,0,0,.5);
    --shadow-2: 0 4px 12px rgba(0,0,0,.55);
    --shadow-3: 0 14px 36px rgba(0,0,0,.7);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --rail-bg: #1a181f;
  --canvas: #141218; --surface: #1d1b20; --surface-2: #24222a;
  --control: #322f3a; --control-hi: #423e4b;
  --line: #49454f; --line-soft: #2b2831;
  --primary: #cfbcff; --primary-ink: #381e72;
  --primary-cont: #4f378a; --on-primary-cont: #e9ddff;
  --ink: #e7e1ec; --ink-2: #cac4d0; --ink-3: #aaa3b5;
  --danger: #ffb4ab; --danger-cont: #601410; --danger-edge: #ff8a80;
  --on-danger: #601410; --on-hold: #3a1f00; --on-ok: #0b2a15;
  --hold: #ffb95c; --hold-cont: #4a2800;
  --watch: #cfbcff; --watch-cont: #3c2a68;
  --ok: #7fdba2; --ok-cont: #17351f;
  --shadow-1: 0 1px 2px rgba(0,0,0,.5);
  --shadow-2: 0 4px 12px rgba(0,0,0,.55);
  --shadow-3: 0 14px 36px rgba(0,0,0,.7);
}
:root[data-theme="light"] { color-scheme: light; }

* { box-sizing: border-box; }

/* Half the elements below set an explicit display, which beats the user agent's
   [hidden] { display: none } on specificity. Everything in this file that is
   ever shown or hidden from script depends on this one line. */
[hidden] { display: none !important; }

html, body {
  height: 100%; margin: 0;
  background: var(--canvas); color: var(--ink);
  font-family: var(--font);
  font-size: 14px; line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

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

:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 4px; }

.icon {
  width: 20px; height: 20px; flex: 0 0 auto; fill: none;
  stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.icon.sm { width: 16px; height: 16px; }
.icon.lg { width: 24px; height: 24px; }

/* ========================================================================== */
/* Shell                                                                      */
/* ========================================================================== */

.app { display: grid; grid-template-columns: var(--rail) 1fr; height: 100vh; }
.app.rail-collapsed { grid-template-columns: 64px 1fr; }

/* ------------------------------------------------------------------ rail -- */

/* grid-template-COLUMNS matters as much as the rows here. A grid with only its
   rows declared gets one implicit column sized to max-content, so the moment
   anything inside refused to shrink — the nowrap wordmark below — that column
   grew past the 258px track the rail actually occupies, and every child laid
   itself out against the wrong width. The visible symptom was the selected pill
   and the collapse button hanging over the rail's right edge into the content
   area. minmax(0, 1fr) pins the column to the track. */
.side { display: grid; grid-template-rows: auto 1fr auto; grid-template-columns: minmax(0, 1fr);
        min-height: 0; min-width: 0; overflow: hidden;
        background: var(--rail-bg); border-right: 1px solid var(--line); }

.side-head { display: flex; align-items: center; gap: 9px; height: 60px; padding: 0 8px 0 14px; }
.side-head .mark {
  display: grid; place-items: center; width: 30px; height: 30px; flex: 0 0 auto;
  border-radius: 8px; background: var(--primary); color: var(--primary-ink);
}
.side-head .word {
  /* min-width: 0 is what lets this shrink. A flex item defaults to min-width
     auto, so a nowrap label pushes its whole row wider rather than clipping. */
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
  font-size: 12.5px; font-weight: 700; letter-spacing: .02em;
  text-transform: uppercase; color: var(--ink); white-space: nowrap;
}
.side-head .spacer { flex: 1 1 auto; }

.side-scroll { overflow-y: auto; overflow-x: hidden; padding: 4px 0 12px;
               min-height: 0; min-width: 0; }

.nav-label {
  padding: 14px 16px 6px; font-size: 10.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-3);
}

/* A full-height pill rather than a left bar: it is the Material convention and
   it survives the rail collapsing to icons. */
.nav-item {
  display: flex; align-items: center; gap: 12px; width: calc(100% - 16px);
  margin: 1px 8px; padding: 0 12px; height: 40px;
  border: 0; border-radius: var(--r-pill); background: none;
  font: inherit; font-size: 13.5px; color: var(--ink-2); text-align: left; cursor: pointer;
}
.nav-item:hover { background: var(--control); }

/* Two different kinds of selection live in this rail — which VIEW you are on,
   and which CLASS you are looking at — and giving them the same filled pill made
   the rail read as one list with two current items. The view keeps the Material
   pill because it is genuinely "where am I"; the class gets a quieter fill and
   an accent bar, which still reads as chosen without competing. */
.nav-item[aria-current="page"] {
  background: var(--primary-cont); color: var(--on-primary-cont); font-weight: 600;
}
.nav-item.on {
  background: var(--control); color: var(--ink); font-weight: 600;
  position: relative;
}
.nav-item.on::before {
  content: ""; position: absolute; left: 4px; top: 50%; transform: translateY(-50%);
  width: 3px; height: 20px; border-radius: 2px; background: var(--primary);
}
.rail-collapsed .nav-item.on::before { left: 2px; }
.nav-item span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-item .tally {
  margin-left: auto; font-size: 11.5px; font-weight: 600; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.nav-item[aria-current="page"] .tally { color: inherit; opacity: .8; }
.nav-item.on .tally { color: var(--ink-2); }
.nav-item.nav-add { color: var(--primary); font-weight: 600; }

.side-foot { padding: 10px 16px; border-top: 1px solid var(--line-soft);
             font-size: 11px; color: var(--ink-3); }

.rail-collapsed .side-head { padding: 0; justify-content: center; }
.rail-collapsed .side-head .word,
.rail-collapsed .side-head .spacer,
.rail-collapsed .nav-label,
.rail-collapsed .nav-item span,
.rail-collapsed .nav-item .tally,
.rail-collapsed .side-foot { display: none; }
.rail-collapsed .nav-item { justify-content: center; padding: 0; margin: 2px 8px; width: 48px; }

/* ----------------------------------------------------------------- main -- */

.main { display: grid; grid-template-rows: auto auto auto auto 1fr;
        grid-template-columns: minmax(0, 1fr); min-height: 0; min-width: 0; }

.topbar { display: flex; align-items: center; gap: 12px; height: 60px; padding: 0 18px 0 20px; }
.topbar h1 {
  min-width: 0;
  margin: 0; font-size: 21px; font-weight: 600; letter-spacing: -.01em; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.topbar h1 b { font-weight: 600; color: var(--primary); }
.topbar .spacer { flex: 1 1 auto; }

.chip {
  display: inline-flex; align-items: center; gap: 7px; height: 32px; padding: 0 12px;
  border-radius: var(--r-pill); background: var(--control); color: var(--ink-2);
  font-size: 12.5px; white-space: nowrap;
}
.chip .pulse { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); flex: 0 0 auto; }
.chip .pulse.beat { animation: beat 2.4s ease-in-out infinite; }
.chip .pulse.warn { background: var(--hold); animation: none; }
.chip .pulse.dead { background: var(--danger); animation: none; }
@keyframes beat { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .chip .pulse.beat { animation: none; } }

.avatar-btn {
  display: grid; place-items: center; width: 34px; height: 34px; flex: 0 0 auto;
  border: 0; border-radius: 50%; background: var(--primary); color: var(--primary-ink);
  font: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer;
}

/* -------------------------------------------------------------- session -- */

/* The row of round buttons is the tool's instrument panel. One row, always the
   same order, so the muscle memory holds from lesson to lesson. */
.session { display: flex; align-items: flex-start; gap: 10px; padding: 0 20px 12px; flex-wrap: wrap; }

.timer {
  display: inline-flex; align-items: center; gap: 8px; height: 44px; padding: 0 16px;
  border-radius: var(--r-pill); background: var(--control); color: var(--ink);
  font-variant-numeric: tabular-nums; font-weight: 600; font-size: 14px;
}
.timer .icon { color: var(--hold); }
.timer.idle { color: var(--ink-3); font-weight: 500; }
.timer.idle .icon { color: var(--ink-3); }

.instruments { display: flex; align-items: flex-start; gap: 6px; }
.session .spacer { flex: 1 1 auto; }

.instrument { display: grid; justify-items: center; gap: 3px; }
/* Icon-only toolbars are the single most common thing teachers report being
   unable to use. Eleven pixels of text removes the guessing. */
.instrument .lab {
  font-size: 10.5px; color: var(--ink-3); white-space: nowrap;
  max-width: 76px; overflow: hidden; text-overflow: ellipsis;
}
.instrument .round:disabled + .lab { opacity: .45; }

.round {
  display: grid; place-items: center; width: 44px; height: 44px; flex: 0 0 auto;
  border: 0; border-radius: 50%; background: var(--control); color: var(--ink); cursor: pointer;
}
.round:hover:not(:disabled) { background: var(--control-hi); }
.round:disabled { opacity: .4; cursor: not-allowed; }
.round.on { background: var(--primary); color: var(--primary-ink); }
.round.danger { background: var(--danger-cont); color: var(--danger); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: 40px; padding: 0 18px; border-radius: var(--r-pill);
  border: 1px solid transparent; background: var(--control); color: var(--ink);
  font: inherit; font-size: 13.5px; font-weight: 500; cursor: pointer; white-space: nowrap;
}
.btn:hover:not(:disabled) { background: var(--control-hi); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn.primary { background: var(--primary); color: var(--primary-ink); }
.btn.primary:hover:not(:disabled) { filter: brightness(1.08); }
.btn.tonal { background: var(--primary-cont); color: var(--on-primary-cont); }
.btn.danger { background: var(--danger); color: var(--on-danger); }
.btn.danger:hover:not(:disabled) { filter: brightness(1.08); }
.btn.ghost { background: none; border-color: var(--line); color: var(--ink-2); }
.btn.ghost:hover:not(:disabled) { background: var(--control); }
.btn.sm { height: 32px; padding: 0 12px; font-size: 12.5px; }
.btn.icon-only { width: 40px; padding: 0; }
.btn.sm.icon-only { width: 32px; }

/* --------------------------------------------------------------- subbar -- */

.subbar { display: flex; align-items: center; gap: 10px; padding: 0 20px 10px; flex-wrap: wrap; }
.subbar .spacer { flex: 1 1 auto; }
.subbar .count { font-size: 12.5px; color: var(--ink-3); }

.check {
  display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 12px 0 10px;
  border-radius: var(--r-pill); background: var(--surface); border: 1px solid var(--line);
  font-size: 12.5px; color: var(--ink-2); cursor: pointer;
}
.check input { width: 15px; height: 15px; accent-color: var(--primary); margin: 0; }

/* Material's segmented button: one control, mutually exclusive options, and the
   current one is obvious without reading it. */
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-pill); overflow: hidden; }
.seg button {
  display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 13px;
  border: 0; background: var(--surface); font: inherit; font-size: 12.5px;
  color: var(--ink-2); cursor: pointer;
}
.seg button + button { border-left: 1px solid var(--line); }
.seg button[aria-pressed="true"] { background: var(--primary-cont); color: var(--on-primary-cont); font-weight: 600; }
.seg button:hover:not([aria-pressed="true"]) { background: var(--control); }

/* ---------------------------------------------------------- conditions -- */

/* Standing facts about how the tool behaves — active hours, the pupil-visible
   notice, the audit log. These were once dismissible banners that pushed every
   pupil below the fold on load. They are not notifications; they are furniture. */
.conditions {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px;
  padding: 0 20px 12px; font-size: 11.5px; color: var(--ink-3);
}
.conditions .item { display: inline-flex; align-items: center; gap: 6px; }
.conditions .icon { width: 14px; height: 14px; opacity: .75; }

/* ========================================================================== */
/* Views                                                                      */
/* ========================================================================== */

.scrollarea { overflow-y: auto; padding: 0 20px 24px; min-height: 0; }

.band-head {
  display: flex; align-items: center; gap: 8px; margin: 2px 0 8px;
  font-size: 11.5px; font-weight: 700; letter-spacing: .075em;
  text-transform: uppercase; color: var(--ink-3);
}
.band-head .tally {
  display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding: 0 6px;
  border-radius: var(--r-pill); background: var(--control); color: var(--ink-2);
  font-size: 11px; letter-spacing: 0;
}
.band-head.urgent { color: var(--danger); }
.band-head.urgent .tally { background: var(--danger-cont); color: var(--danger); }
.band-head .rule { flex: 1 1 auto; height: 1px; background: var(--line); }

#needsYouSection { margin-bottom: 18px; }

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

.grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(var(--card-w), 1fr)); }

.card {
  display: flex; flex-direction: column; overflow: hidden; position: relative;
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: var(--r-card); box-shadow: var(--shadow-1); cursor: pointer;
}
.card:hover { box-shadow: var(--shadow-2); }
.card.selected { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary), var(--shadow-2); }
/* The one state allowed to shout. */
.card.s-help { border-color: var(--danger-edge); box-shadow: 0 0 0 1px var(--danger-edge), var(--shadow-2); }
.card.s-off { opacity: .66; }

.card-head { display: flex; align-items: center; gap: 6px; padding: 9px 6px 7px 12px; min-height: 40px; }
.card-name {
  flex: 1 1 auto; min-width: 0; font-size: 13.5px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.card-ctl { display: flex; align-items: center; gap: 1px; flex: 0 0 auto; }
.card-ctl input[type="checkbox"] { width: 15px; height: 15px; accent-color: var(--primary); margin: 0 4px; }

.mini {
  display: grid; place-items: center; width: 28px; height: 28px;
  border: 0; border-radius: 50%; background: none; color: var(--ink-3); cursor: pointer;
}
.mini:hover:not(:disabled) { background: var(--control); color: var(--ink); }
.mini:disabled { opacity: .35; cursor: not-allowed; }
.mini.on { background: var(--control); color: var(--primary); }
.mini .icon { width: 17px; height: 17px; }

/* The line answering "what are they actually doing" — the reason a teacher
   opened this screen at all. */
.card-tab { display: flex; align-items: center; gap: 7px; padding: 0 12px 9px; min-width: 0; font-size: 12px; }
.card-tab img { width: 14px; height: 14px; border-radius: 3px; flex: 0 0 auto; }
.card-tab .t {
  flex: 1 1 auto; min-width: 0; color: var(--primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.card-tab .t.quiet { color: var(--ink-3); }
.card-tab .tallies { display: flex; align-items: center; gap: 9px; flex: 0 0 auto;
                     color: var(--ink-3); font-size: 11.5px; }
.card-tab .tallies span { display: inline-flex; align-items: center; gap: 3px; font-variant-numeric: tabular-nums; }
.card-tab .tallies .icon { width: 13px; height: 13px; }

/* The flag beside the page a pupil is on. Neutral until you reach for it: the
   rule that red means "a pupil needs you" is worth more than making this
   button findable, and thirty red flags across a class would spend it. */
.report {
  display: grid; place-items: center; width: 22px; height: 22px; flex: 0 0 auto;
  border: 0; border-radius: 6px; background: none; color: var(--ink-3); cursor: pointer;
}
.report .icon { width: 14px; height: 14px; }
.report:hover { background: var(--danger-cont); color: var(--danger); }
.card:hover .report { color: var(--ink-2); }

/* The address being reported, shown in full so nobody reports the wrong page. */
.reported-url {
  padding: 10px 12px; border-radius: var(--r-ctl);
  background: var(--surface-2); border: 1px solid var(--line);
}
.reported-url .h { font-size: 14px; font-weight: 600; color: var(--ink); }
.reported-url .u {
  margin-top: 3px; font-size: 11.5px; color: var(--ink-3);
  word-break: break-all; max-height: 4.5em; overflow: auto;
}

/* The screen thumbnail. A fixed 16:10 box keeps a Chromebook's aspect without
   letterboxing and stops the grid reflowing every time a frame lands. */
.card-shot {
  position: relative; aspect-ratio: 16 / 10; background: var(--surface-2);
  border-top: 1px solid var(--line-soft); overflow: hidden;
}
.card-shot img { width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block; }
.card-shot .placeholder {
  position: absolute; inset: 0; display: grid; place-content: center; justify-items: center;
  gap: 8px; padding: 8px; color: var(--ink-3); font-size: 11.5px; text-align: center;
}
.card-shot .placeholder .icon { width: 26px; height: 26px; opacity: .5; }
.card-shot .age:empty { display: none; }
.card-shot .age {
  position: absolute; right: 6px; bottom: 6px; padding: 1px 6px; border-radius: var(--r-pill);
  background: rgba(0, 0, 0, .55); color: #fff; font-size: 10px; font-variant-numeric: tabular-nums;
}
.card-shot.stale img { filter: grayscale(.55) opacity(.6); }

.card-foot {
  display: flex; align-items: center; gap: 6px; padding: 7px 8px 7px 10px;
  border-top: 1px solid var(--line-soft); min-height: 42px;
}
.card-foot .spacer { flex: 1 1 auto; }

/* ---------------------------------------------------------------- state -- */

.state {
  display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto;
  height: 22px; padding: 0 9px; border-radius: var(--r-pill);
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
  background: var(--control); color: var(--ink-2);
}
.state .icon { width: 13px; height: 13px; }
.state.help  { background: var(--danger-cont); color: var(--danger); }
.state.lock  { background: var(--hold-cont);   color: var(--hold); }
.state.watch { background: var(--watch-cont);  color: var(--watch); }
.state.limit { background: var(--control);     color: var(--ink-2); }
.state.off   { background: var(--control);     color: var(--ink-3); }
.state.ok    { background: var(--ok-cont);     color: var(--ok); }

/* ------------------------------------------------------------ list view -- */

/* The same roll, one line per pupil. A full class fits on one screen with no
   scrolling, which is what a register wants to be. */
.roll { display: grid; gap: 3px; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); }

.pupil {
  display: flex; align-items: center; gap: 10px; position: relative;
  min-height: 44px; padding: 0 10px 0 8px;
  border-radius: var(--r-ctl); background: var(--surface);
  border: 1px solid var(--line-soft); cursor: pointer;
}
.pupil:hover { background: var(--surface-2); }
.pupil.selected { border-color: var(--primary); box-shadow: inset 0 0 0 1px var(--primary); }
.pupil.s-help { border-left: 3px solid var(--danger-edge); padding-left: 6px; }
.pupil.s-off { opacity: .6; }

.avatar {
  display: grid; place-items: center; width: 28px; height: 28px; flex: 0 0 auto;
  border-radius: 50%; background: var(--control); color: var(--ink-2);
  font-size: 11px; font-weight: 700;
}
.pupil.s-help .avatar { background: var(--danger-cont); color: var(--danger); }

.who { flex: 0 0 132px; min-width: 0; }
.who .name { font-size: 13.5px; font-weight: 600; color: var(--ink);
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who .sub { font-size: 11px; color: var(--ink-3); }

.doing { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px; }
.doing img { width: 15px; height: 15px; border-radius: 3px; flex: 0 0 auto; }
.doing .t { min-width: 0; }
.doing .title { font-size: 12.5px; color: var(--ink-2);
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.doing .host { font-size: 10.5px; color: var(--ink-3);
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.doing .quiet { font-size: 12.5px; color: var(--ink-3); }

/* Absolutely positioned on purpose: laid out in flow at opacity 0 this strip
   still claimed about 130px from every row, which truncated page titles even
   when nobody was hovering. */
.acts {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; gap: 3px; padding-left: 14px;
  background: linear-gradient(to right, transparent, var(--surface-2) 14px);
  opacity: 0; pointer-events: none;
}
.pupil:hover .acts, .pupil:focus-within .acts { opacity: 1; pointer-events: auto; }
@media (hover: none) { .acts { opacity: 1; pointer-events: auto; } }

/* A pupil who needs the teacher should not have to be hovered to be helped: the
   two buttons that answer a raised hand are always there. The rest still wait
   for hover, so at rest the overlay stays narrow enough to leave the page title
   — the thing you most want to see on a help row — readable. */
.acts.always { opacity: 1; pointer-events: auto; }
.acts.always .btn:not(.keep) { display: none; }
.pupil:hover .acts.always .btn,
.pupil:focus-within .acts.always .btn { display: inline-flex; }

.acts .btn { height: 30px; width: 30px; padding: 0; background: var(--control); }
.acts .btn:hover:not(:disabled) { background: var(--control-hi); }
.acts .btn.primary { width: auto; padding: 0 12px; font-weight: 600; background: var(--primary); }

/* ---------------------------------------------------------------- folds -- */

.offline-fold {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 14px; padding: 9px 12px;
  border: 1px dashed var(--line); border-radius: var(--r-ctl);
  font-size: 12px; color: var(--ink-3);
}
.offline-fold .icon { opacity: .6; }

.empty { display: grid; justify-items: center; gap: 10px; padding: 60px 20px;
         color: var(--ink-3); text-align: center; }
.empty .icon { width: 34px; height: 34px; opacity: .45; }
.empty h3 { margin: 0; font-size: 15px; color: var(--ink-2); font-weight: 600; }
.empty p { margin: 0; max-width: 44ch; font-size: 12.5px; }

/* ========================================================================== */
/* Panels, tables, menus, modals                                              */
/* ========================================================================== */

.panel {
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: var(--r-card); box-shadow: var(--shadow-1);
  margin-bottom: 14px; overflow: hidden;
}
.panel > header { display: flex; align-items: center; gap: 10px; padding: 12px 14px;
                  border-bottom: 1px solid var(--line-soft); }
.panel > header h2 { margin: 0; font-size: 13.5px; font-weight: 600; color: var(--ink); }
.panel > header .spacer { flex: 1 1 auto; }
.panel .inner { padding: 14px; }
.panel .scroll { max-height: 420px; overflow: auto; }

table.data { width: 100%; border-collapse: collapse; font-size: 12.5px; }
table.data th {
  position: sticky; top: 0; z-index: 1; text-align: left; font-weight: 600;
  padding: 9px 12px; background: var(--surface-2); color: var(--ink-3);
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
table.data td { padding: 9px 12px; border-bottom: 1px solid var(--line-soft);
                color: var(--ink-2); vertical-align: top; }
table.data tr:last-child td { border-bottom: 0; }
table.data tr:hover td { background: var(--surface-2); }
.mono { font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace; font-size: 11.5px; }

.menu-wrap { position: relative; }
.menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 40; min-width: 236px;
  padding: 6px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-ctl); box-shadow: var(--shadow-3);
}
.menu button {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px;
  border: 0; border-radius: 8px; background: none; font: inherit; font-size: 13px;
  color: var(--ink-2); text-align: left; cursor: pointer;
}
.menu button:hover:not(:disabled) { background: var(--control); color: var(--ink); }
.menu button:disabled { opacity: .4; cursor: not-allowed; }
.menu button .icon { color: var(--ink-3); }
.menu .sep { height: 1px; margin: 5px 4px; background: var(--line-soft); }
.menu .lab { padding: 8px 10px 4px; font-size: 10.5px; font-weight: 700;
             letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); }

.overlay { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center;
           padding: 20px; background: rgba(20, 18, 24, .55); }
.modal { width: min(560px, 100%); max-height: 90vh; overflow: auto;
         background: var(--surface); border-radius: 16px; box-shadow: var(--shadow-3); }
.modal header { display: flex; align-items: center; gap: 10px; padding: 16px 18px; background: var(--canvas); }
.modal header h2 { margin: 0; font-size: 16px; font-weight: 600; color: var(--ink); }
.modal header .spacer { flex: 1 1 auto; }
.modal .body { padding: 18px; display: grid; gap: 14px; }
.modal footer { display: flex; justify-content: flex-end; gap: 10px; padding: 14px 18px;
                border-top: 1px solid var(--line-soft); }

.field { display: grid; gap: 6px; }
.field label { font-size: 12px; font-weight: 600; color: var(--ink-2); }
.field .hint { font-size: 11.5px; color: var(--ink-3); }

input[type="text"], input[type="url"], input[type="email"], input[type="number"],
textarea, select {
  width: 100%; padding: 10px 12px; border-radius: var(--r-ctl);
  border: 1px solid var(--line); background: var(--surface-2); color: var(--ink);
  font: inherit; font-size: 13.5px;
}
textarea { min-height: 100px; resize: vertical; }
input:focus, textarea:focus, select:focus { outline: 2px solid var(--primary); outline-offset: -1px; border-color: transparent; }
select { cursor: pointer; }

.close { display: grid; place-items: center; width: 34px; height: 34px;
         border: 0; border-radius: 50%; background: none; color: var(--ink-2); cursor: pointer; }
.close:hover { background: var(--control); }

/* --------------------------------------------------------------- viewer -- */

.viewer {
  width: min(1100px, 100%); max-height: 92vh; display: flex; flex-direction: column;
  background: var(--surface); border-radius: 16px; box-shadow: var(--shadow-3); overflow: hidden;
}
.viewer header { display: flex; align-items: center; gap: 10px; padding: 14px 16px; background: var(--canvas); }
.viewer header h2 { margin: 0; font-size: 15px; font-weight: 600; }
.viewer header .spacer { flex: 1 1 auto; }
.viewer .stage { display: grid; place-items: center; min-height: 340px; padding: 14px; background: #101014; }
.viewer .stage img { max-width: 100%; max-height: 62vh; border-radius: 8px; display: block; }
.viewer .stage .placeholder { color: #b9b6c4; font-size: 13px; text-align: center; }
.viewer .tabs-list { max-height: 190px; overflow: auto; border-top: 1px solid var(--line-soft); }
.viewer footer { display: flex; align-items: center; gap: 10px; padding: 12px 16px;
                 border-top: 1px solid var(--line-soft); }
.viewer footer .spacer { flex: 1 1 auto; }

.tabrow { display: flex; align-items: center; gap: 10px; padding: 9px 16px;
          border-bottom: 1px solid var(--line-soft); }
.tabrow:last-child { border-bottom: 0; }
.tabrow img { width: 15px; height: 15px; flex: 0 0 auto; }
.tabrow .t { flex: 1 1 auto; min-width: 0; }
.tabrow .t .title { font-size: 12.5px; color: var(--ink);
                    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tabrow .t .u { font-size: 11px; color: var(--ink-3);
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tabrow.active .t .title { font-weight: 600; }

/* --------------------------------------------------------------- toasts -- */

.toasts { position: fixed; right: 18px; bottom: 18px; z-index: 90; display: grid; gap: 8px; justify-items: end; }
.toast {
  display: flex; align-items: center; gap: 9px; max-width: 400px; padding: 11px 14px;
  border-radius: var(--r-ctl); background: var(--ink); color: var(--canvas);
  font-size: 13px; box-shadow: var(--shadow-3);
}
.toast.err { background: var(--danger); color: var(--on-danger); }
.toast.warn { background: var(--hold); color: var(--on-hold); }
.toast.ok { background: var(--ok); color: var(--on-ok); }

.banner {
  display: flex; align-items: center; gap: 10px; margin-bottom: 12px; padding: 11px 14px;
  border-radius: var(--r-ctl); background: var(--control); color: var(--ink-2); font-size: 12.5px;
}
.banner.warn { background: var(--hold-cont); color: var(--hold); }
.banner.err { background: var(--danger-cont); color: var(--danger); }
.banner .spacer { flex: 1 1 auto; }

.note { font-size: 12px; color: var(--ink-3); }
.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.quiet { color: var(--ink-3); }

/* -------------------------------------------------------------- narrow -- */

@media (max-width: 1040px) {
  .app { grid-template-columns: 64px 1fr; }
  .app .nav-label, .app .nav-item span, .app .nav-item .tally, .app .side-foot,
  .app .side-head .word, .app .side-head .spacer { display: none; }
  .app .nav-item { justify-content: center; padding: 0; margin: 2px 8px; width: 48px; }
  .app .side-head { padding: 0; justify-content: center; }
  .roll { grid-template-columns: 1fr; }
  .instrument .lab { display: none; }
}
