/* Admin panel — base styles
   ---------------------------------------------------------------------------
   Layout and component rules only. Every colour and font comes from the
   custom properties in theme.css (loaded first); edit that file to recolour
   the app. Do not introduce raw hex values here.

   Design language (shared with the Ledger + GymSuite): hairline borders
   instead of shadows, 8px controls, 14px cards, a recessed segmented track
   with a raised active chip, uppercase muted group labels, tabular numbers.
   The reusable primitives live under "Primitive layer" below (ui-*); the
   older cx-* / btn / pill / card classes are restyled to the same language
   so existing screens pick it up without edits.
*/

* { box-sizing: border-box; }
html, body, #root { height: 100%; }
body {
  margin: 0;
  font-family: var(--font-sans);
  background-color: var(--bg-0);
  color: var(--ink-1);
  font-size: 13px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
.mono { font-family: var(--font-mono); }
.serif { font-family: var(--font-display); font-weight: 400; }
.tabular, .num { font-variant-numeric: tabular-nums; }

button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }
input, textarea, select { font-family: inherit; }
:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 2px; }

/* Scrollbars */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--grey-solid); border-radius: 6px; border: 2px solid var(--bg-0); }
::-webkit-scrollbar-thumb:hover { background: var(--ink-4); }

/* ============ App shell ============
   Sidebar (sticky, full height) beside a main column. The window still
   scrolls — screens and the radial widget read window.scrollY — so the
   sidebar is sticky rather than the main column being a scroller. */
.app {
  display: flex;
  min-height: 100vh;
  min-height: 100dvh;
}
.main {
  display: flex; flex-direction: column;
  min-width: 0; flex: 1;
}
.content { flex: 1; min-height: 0; min-width: 0; }

/* ---- Sidebar ---- */
.nv-sidebar {
  position: sticky; top: 0; align-self: flex-start;
  width: var(--sidebar-w); flex-shrink: 0;
  height: 100vh; height: 100dvh;
  overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: thin;
  background: var(--surface);
  border-right: 1px solid var(--line);
  padding: calc(12px + env(safe-area-inset-top, 0px)) 12px 24px
           calc(12px + env(safe-area-inset-left, 0px));
  display: flex; flex-direction: column; gap: 20px;
}
.nv-brand {
  display: flex; align-items: center; gap: 10px;
  min-width: 0; padding: 6px 8px; min-height: 44px;
}
.nv-brand-mark {
  width: 30px; height: 30px; flex-shrink: 0;
  display: grid; place-items: center;
  border-radius: var(--radius-ctl); overflow: hidden;
}
.nv-brand-mark img { width: 30px; height: 30px; object-fit: contain; }
.nv-brand-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.nv-brand-name {
  font-size: 15px; font-weight: 600; letter-spacing: -0.2px; color: var(--ink-0);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nv-brand-sub {
  font-size: 11.5px; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.nv-nav { display: flex; flex-direction: column; gap: 18px; }
.nv-nav-group { display: flex; flex-direction: column; gap: 2px; }
.nv-nav-label {
  padding: 0 10px 6px;
  font-size: 11px; font-weight: 500; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-3);
}
.nv-nav-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; min-height: 36px; padding: 0 10px;
  border-radius: var(--radius-ctl);
  font-size: 13.5px; font-weight: 450; color: var(--ink-2);
  text-align: left;
  transition: background 0.12s, color 0.12s;
}
.nv-nav-item svg { flex-shrink: 0; color: var(--ink-3); transition: color 0.12s; }
.nv-nav-item:hover { background: var(--surface-hover); color: var(--ink-0); }
.nv-nav-item:hover svg { color: var(--ink-1); }
.nv-nav-item.active { background: var(--surface-active); color: var(--ink-0); font-weight: 600; }
.nv-nav-item.active svg { color: var(--ink-0); }
.nv-nav-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.nv-nav-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nv-nav-count {
  font-size: 11px; font-weight: 500; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  padding: 1px 7px; border-radius: 999px; background: var(--bg-3);
}
.nv-nav-item.active .nv-nav-count { background: var(--surface); }

/* Drawer (below 1024px): the same sidebar, fixed and slid in. */
.nv-drawer {
  position: fixed; left: 0; top: 0; bottom: 0; z-index: 270;
  width: min(300px, 86vw); height: auto;
  box-shadow: var(--shadow-pop);
  animation: nv-drawer-in 0.22s cubic-bezier(.2,.85,.3,1);
}
.nv-drawer-scrim {
  position: fixed; inset: 0; z-index: 265;
  background: var(--overlay);
  animation: cx-fade-in 0.18s ease-out;
}
@keyframes nv-drawer-in { from { transform: translateX(-100%); } to { transform: translateX(0); } }

/* ---- Top bar ---- */
.nv-topbar {
  position: sticky; top: 0; z-index: 150;
  display: flex; align-items: center; gap: 10px;
  min-height: calc(var(--topbar-h) + env(safe-area-inset-top, 0px));
  padding: env(safe-area-inset-top, 0px) calc(24px + env(safe-area-inset-right, 0px)) 0 32px;
  background: color-mix(in srgb, var(--bg-0) 88%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(105%);
  backdrop-filter: blur(12px) saturate(105%);
  border-bottom: 1px solid var(--line);
}
.nv-topbar .nv-menu-btn, .nv-topbar .nv-topbar-mark { display: none; }
.nv-topbar-mark img { width: 26px; height: 26px; }
.nv-crumbs {
  display: flex; align-items: center; gap: 6px;
  min-width: 0; font-size: 13px; color: var(--ink-3);
}
.nv-crumbs > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nv-crumbs .here { color: var(--ink-0); font-weight: 500; }
.nv-crumb-sep { color: var(--ink-4); flex-shrink: 0; }
.nv-topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 6px; flex-shrink: 0; }

/* Round ghost icon button — topbar bell, back buttons, card corner actions. */
.cx-round-btn {
  width: 36px; height: 36px; border-radius: 50%;
  border: 1px solid transparent; background: transparent;
  display: grid; place-items: center; color: var(--ink-1);
  position: relative; flex-shrink: 0;
  transition: background 0.12s, color 0.12s;
}
.cx-round-btn:hover { background: var(--bg-3); color: var(--ink-0); }
.cx-round-btn.sm { width: 32px; height: 32px; }
.cx-round-btn.xs { width: 28px; height: 28px; }
.cx-card .cx-round-btn:hover, .card .cx-round-btn:hover { background: var(--bg-2); }
.cx-badge-count {
  position: absolute; top: 1px; right: -1px;
  background: var(--pink-solid); color: var(--on-solid);
  font-size: 10px; font-weight: 600; font-variant-numeric: tabular-nums;
  border-radius: 999px; padding: 1px 5px; min-width: 18px; text-align: center;
  border: 2px solid var(--bg-0);
  line-height: 1.2;
}
/* Round initials avatar (account menu trigger). */
.cx-avatar-btn {
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--bg-3); border: 1px solid var(--line-strong);
  display: grid; place-items: center;
  color: var(--ink-1); font-size: 12px; font-weight: 600; letter-spacing: 0.02em;
  flex-shrink: 0;
  transition: background 0.12s;
}
.cx-avatar-btn:hover { background: var(--bg-4); }

/* ---- Section pages strip (narrow screens only) ---- */
.cx-subnavwrap { display: none; }
.cx-subnav {
  display: flex; align-items: center; gap: 2px;
  padding: 3px; background: var(--track);
  border-radius: var(--radius-seg);
  overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
}
.cx-subnav::-webkit-scrollbar { display: none; }
.cx-subnav-item {
  flex: 0 0 auto; scroll-snap-align: start;
  min-height: 32px; padding: 0 12px;
  border-radius: 7px;
  font-size: 13px; font-weight: 500; color: var(--ink-3);
  white-space: nowrap;
  transition: background 0.12s, color 0.12s;
}
.cx-subnav-item:hover { color: var(--ink-0); }
.cx-subnav-item.active { background: var(--surface); color: var(--ink-0); box-shadow: var(--shadow-raised); }
.cx-subnav-item .count { margin-left: 6px; font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }

/* ---- Menus / popovers (account menu, notifications) ---- */
.ui-menu {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow-pop);
  overflow: hidden;
  animation: ui-pop-in 0.14s ease-out;
}
.ui-menu-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border-bottom: 1px solid var(--line-soft);
}
.ui-menu-title { font-size: 14px; font-weight: 600; color: var(--ink-0); }
.ui-menu-sub { font-size: 12px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-menu-item {
  display: flex; align-items: center; gap: 9px;
  width: 100%; min-height: 36px; padding: 0 14px;
  font-size: 13px; font-weight: 500; color: var(--ink-1); text-align: left;
}
.ui-menu-item:hover { background: var(--surface-hover); }
.ui-menu-item.danger { color: var(--red); }
.ui-menu-item.danger:hover { background: var(--red-bg); }

.ui-notif-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px 10px 10px 16px; border-bottom: 1px solid var(--line-soft);
  position: sticky; top: 0; background: var(--surface); z-index: 1;
}
.ui-notif {
  padding: 11px 16px; border-bottom: 1px solid var(--line-soft);
  cursor: pointer; transition: background 0.12s;
}
.ui-notif:hover { background: var(--surface-hover); }
.ui-notif.unread { background: var(--accent-bg); }
.ui-notif-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex-shrink: 0; }
.ui-notif-title { color: var(--ink-0); font-size: 13px; line-height: 1.35; overflow-wrap: anywhere; }
.ui-notif-sub { color: var(--ink-3); font-size: 12px; margin-top: 2px; overflow-wrap: anywhere; }
.ui-notif-all {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; padding: 12px 16px;
  font-size: 13px; font-weight: 500; color: var(--ink-1);
}
.ui-notif-all:hover { background: var(--surface-hover); color: var(--ink-0); }

@keyframes ui-pop-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .ui-menu, .nv-drawer, .nv-drawer-scrim { animation: none; }
}

/* ---- Page scaffolding ---- */
.cx-page { max-width: 1400px; margin: 0 auto; padding: 28px 32px 56px; }
.cx-page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px 20px; margin-bottom: 24px; flex-wrap: wrap;
}
.cx-eyebrow, .ui-eyebrow {
  font-size: 11.5px; font-weight: 500; letter-spacing: 0.06em;
  color: var(--ink-3); text-transform: uppercase; margin-bottom: 6px;
}
.cx-h1 { margin: 0; font-size: 26px; font-weight: 600; letter-spacing: -0.5px; line-height: 1.2; color: var(--ink-0); }
.cx-h2 { margin: 0; font-size: 20px; font-weight: 600; letter-spacing: -0.3px; line-height: 1.25; color: var(--ink-0); }

/* ---- Cards ---- */
.cx-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 22px;
  box-shadow: var(--shadow-card);
  min-width: 0;
}
.cx-card.tight { padding: 18px 20px; }
.cx-card.flush { padding: 0; overflow: hidden; }
.cx-card-title { font-size: 16px; font-weight: 600; letter-spacing: -0.15px; color: var(--ink-0); line-height: 1.3; }
.cx-card-sub { font-size: 12.5px; color: var(--ink-3); margin-top: 2px; }
.cx-card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 16px; flex-wrap: wrap;
}
.cx-card.flush > .cx-card-head { padding: 18px 20px 0; }
.cx-card-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  border-top: 1px solid var(--line);
  padding-top: 14px; margin-top: 18px;
}
.cx-grid { display: grid; gap: 20px; }

/* ---- Stat card (KPI tile): muted label, big number, hint ---- */
.cx-stat { display: flex; flex-direction: column; gap: 14px; }
.cx-stat-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.cx-stat-head > div:first-child { gap: 10px !important; }
.cx-stat-icon {
  width: 32px; height: 32px; border-radius: var(--radius-ctl);
  display: grid; place-items: center; color: var(--on-solid);
  flex-shrink: 0;
  box-shadow: none !important;
}
.cx-stat-icon svg { width: 16px; height: 16px; }
.cx-stat-label { font-size: 13.5px; font-weight: 500; line-height: 1.3; color: var(--ink-2); max-width: 180px; }
.cx-stat-value {
  font-size: 28px; font-weight: 600; letter-spacing: -0.6px; line-height: 1.1;
  color: var(--ink-0); font-variant-numeric: tabular-nums;
}
.cx-stat-value.sm { font-size: 22px; letter-spacing: -0.4px; }
.cx-stat-row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.cx-stat-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: auto; font-size: 12.5px; }
.cx-stat-foot > span { font-size: 12.5px !important; }
.cx-delta { font-size: 12.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
.cx-delta.up   { color: var(--green); }
.cx-delta.down { color: var(--red); }
.cx-delta.warn { color: var(--amber); }
.cx-delta.flat { color: var(--ink-3); }

/* ---- Pills used across the cockpit screens ----
   Default = outlined chip (filters, meta). `.tint` + tone = status pill. */
.cx-pill {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--line-strong);
  border-radius: 999px; padding: 3px 10px; min-height: 24px;
  font-size: 12px; font-weight: 500; color: var(--ink-1); line-height: 1.3;
  background: var(--surface); white-space: nowrap;
}
.cx-pill.tint { border-color: transparent; font-size: 11.5px; padding: 2px 9px; min-height: 22px; }
.cx-pill.green  { background: var(--green-bg);  color: var(--green); border-color: transparent; }
.cx-pill.amber  { background: var(--amber-bg);  color: var(--amber); border-color: transparent; }
.cx-pill.red    { background: var(--red-bg);    color: var(--red);   border-color: transparent; }
.cx-pill.blue   { background: var(--blue-bg);   color: var(--blue);  border-color: transparent; }
.cx-pill.violet { background: var(--violet-bg); color: var(--violet);border-color: transparent; }
.cx-pill.grey   { background: var(--bg-3); color: var(--ink-2); border-color: transparent; }
.cx-pill.dark   { background: var(--primary); color: var(--primary-ink); border-color: transparent; font-weight: 600; }

/* ---- Segmented control (CxSeg) — same as .seg / .ui-tabs ---- */
.cx-seg {
  display: inline-flex; align-items: center; gap: 2px;
  background: var(--track); border: 0;
  border-radius: var(--radius-seg); padding: 3px;
  max-width: 100%; overflow-x: auto; scrollbar-width: none;
}
.cx-seg::-webkit-scrollbar { display: none; }
.cx-seg button {
  border: none; border-radius: 7px;
  min-height: 30px; padding: 0 14px; font-size: 13px; font-weight: 500;
  background: transparent; color: var(--ink-3);
  white-space: nowrap; transition: background 0.12s, color 0.12s;
}
.cx-seg button:hover { color: var(--ink-0); }
.cx-seg button.active { background: var(--surface); color: var(--ink-0); box-shadow: var(--shadow-raised); }

/* ---- Primary action (the old dark pill): compact solid button ---- */
.cx-btn-dark {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  background: var(--primary); color: var(--primary-ink);
  border: 1px solid transparent; border-radius: var(--radius-ctl);
  min-height: 36px; padding: 0 14px; font-size: 13.5px; font-weight: 500;
  white-space: nowrap;
  transition: background 0.12s, transform 0.06s;
}
.cx-btn-dark:hover { background: var(--primary-hover); }
.cx-btn-dark:active:not(:disabled) { transform: translateY(1px); }
.cx-btn-dark:disabled { opacity: 0.5; cursor: not-allowed; }
.cx-btn-dark.sm { min-height: 32px; padding: 0 12px; font-size: 13px; }
.cx-btn-dark svg { width: 15px; height: 15px; }

/* ---- Rows inside cards ---- */
.cx-row {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
  min-width: 0;
}
.cx-row:last-child { border-bottom: none; }
.cx-row-title { font-size: 14px; font-weight: 500; color: var(--ink-0); }
.cx-row-sub   { font-size: 12.5px; color: var(--ink-3); }
.cx-av {
  width: 36px; height: 36px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 12.5px; font-weight: 600; flex-shrink: 0;
}
.cx-av.sm { width: 32px; height: 32px; font-size: 11.5px; }
.cx-av.lg { width: 44px; height: 44px; font-size: 14px; }

/* ---- Meters ---- */
.cx-meter { height: 6px; border-radius: 999px; background: var(--bg-3); overflow: hidden; }
.cx-meter > div { height: 100%; border-radius: 999px; background: var(--blue-solid); }
.cx-meter.thin { height: 5px; }
.cx-meter.xthin { height: 4px; }

/* ---- Inset tile (the grey squares inside cards) ---- */
.cx-tile {
  background: var(--bg-2); border: 1px solid var(--line-soft);
  border-radius: var(--radius-tile);
  padding: 14px; min-width: 0;
}
.cx-tile-value { font-size: 22px; font-weight: 600; letter-spacing: -0.4px; color: var(--ink-0); font-variant-numeric: tabular-nums; }
.cx-tile-label { font-size: 12.5px; color: var(--ink-3); }

/* ---- Timeline dot (activity feeds) ---- */
.cx-dot {
  width: 8px; height: 8px; border-radius: 50%;
  margin-top: 6px; flex-shrink: 0;
}

/* ---- Empty / not-connected states ---- */
.cx-empty, .ui-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; text-align: center;
  padding: 36px 24px;
  color: var(--ink-3); font-size: 13px;
}
.cx-empty .cx-empty-mark, .ui-empty-mark {
  width: 40px; height: 40px; border-radius: var(--radius-tile);
  background: var(--bg-3); color: var(--ink-3);
  display: grid; place-items: center; margin-bottom: 4px;
}
.cx-empty strong, .ui-empty-title { color: var(--ink-0); font-weight: 600; display: block; font-size: 14px; }
.cx-empty span { display: block; margin-top: 2px; }
.ui-empty-hint { color: var(--ink-3); font-size: 13px; max-width: 420px; }
.ui-empty-action { margin-top: 8px; display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.ui-empty.compact { padding: 22px 16px; }

.cx-note {
  display: flex; gap: 12px; align-items: center;
  background: var(--bg-2); border: 1px solid var(--line-soft);
  border-radius: var(--radius-tile);
  padding: 12px 14px;
  font-size: 13px; color: var(--ink-2); line-height: 1.45;
}
.cx-note .cx-note-mark {
  width: 32px; height: 32px; border-radius: var(--radius-ctl);
  background: var(--surface); color: var(--ink-1); border: 1px solid var(--line);
  display: grid; place-items: center; flex-shrink: 0;
}


/* ---- Attendance heat-map cell ---- */
.cx-heat { width: 14px; height: 14px; border-radius: 4px; flex-shrink: 0; }
.cx-heat.present    { background: var(--green-solid); }
.cx-heat.leave      { background: var(--amber-solid); }
.cx-heat.absent     { background: var(--red-solid); }
.cx-heat.weekend    { background: var(--bg-3); }
/* Announced holiday: the school was shut, so the blank is explained and the
   day is not counted as a working day (routes/admin_cockpit.ts). Blue so it
   cannot be mistaken for the grey of a weekend or the amber of leave. */
.cx-heat.holiday    { background: var(--blue-bg); border: 1px solid var(--blue-solid); }
.cx-heat.unrecorded { background: var(--surface); border: 1px solid var(--line-strong); }
.cx-heat.future     { background: var(--surface); border: 1px dashed var(--line-strong); }
/* Today's marker. Deliberately a thin ring: at 2px on an unrecorded (white)
   cell it reads as a filled black dot and becomes the loudest thing in a grid
   of hundreds of cells. */
.cx-heat.today      { box-shadow: 0 0 0 1.5px var(--ink-2); }

/* ---- Skeleton grid: three across on desktop, stacked on a phone ---- */
.cx-skelgrid {
  display: grid; gap: 20px;
  grid-template-columns: repeat(var(--cx-skel-cols, 3), minmax(0, 1fr));
}
@media (max-width: 760px) {
  .cx-skelgrid { grid-template-columns: minmax(0, 1fr); gap: 14px; }
}

/* ---- Skeleton shimmer while a screen's data is in flight ---- */
.cx-skel {
  background: linear-gradient(90deg, var(--bg-3) 25%, var(--bg-2) 37%, var(--bg-3) 63%);
  background-size: 400% 100%;
  animation: cx-shimmer 1.3s ease-in-out infinite;
  border-radius: 8px;
}
@keyframes cx-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
@media (prefers-reduced-motion: reduce) { .cx-skel { animation: none; } }

/* ---- Catalog screens (Classes & Subjects, Chapters, Topics) ----
   A picker column beside a detail pane. The column widths used to be fixed
   px, which is what pushed these three screens past the viewport on a phone;
   `minmax(0, …)` lets both tracks actually shrink, and under 900px the pane
   moves below the picker instead of fighting it for width. */
.cx-catalog-split {
  display: grid;
  grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
  gap: 18px;
}
.cx-catalog-split.narrow { grid-template-columns: minmax(0, 280px) minmax(0, 1fr); }
.cx-catalog-split.even   { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
/* `flush` is the full-height editor variant (Answer Keys): no gap, the panes
   own their own padding and scroll independently. */
.cx-catalog-split.flush  { gap: 0; }
/* Without this a grid child refuses to shrink below its content, which is the
   whole reason these screens were 180px wider than the phone. */
.cx-catalog-split > * { min-width: 0; }
@media (max-width: 900px) {
  .cx-catalog-split,
  .cx-catalog-split.narrow,
  .cx-catalog-split.even,
  .cx-catalog-split.flush { grid-template-columns: minmax(0, 1fr); }
  /* A pane that was sized to fill a viewport-height column becomes a
     scroll trap once stacked; let both panes size to content instead. */
  .cx-catalog-split.flush > * { min-height: 0; height: auto; overflow: visible; }
}

/* ---- Student roster ----------------------------------------------------
   Six columns totalling 510px of fixed track plus a flexible name column:
   704px of content in a 332px card. On a phone that pushed the whole page
   sideways, which is why the roster looked "shifted" with its left edge cut
   off. Below 760px the row becomes two lines — identity, then the metadata
   that used to be its own columns. */
.cx-roster-split { display: grid; grid-template-columns: minmax(0, 1fr); }
.cx-roster-split.has-detail { grid-template-columns: minmax(0, 1fr) minmax(0, 420px); }
.cx-roster-split > * { min-width: 0; }

.cx-roster-row {
  display: grid;
  grid-template-columns: 80px minmax(0, 1fr) 120px 90px 140px 80px;
}
.cx-roster-row > * { min-width: 0; }

/* ---- Generic data rows --------------------------------------------------
   The same fixed-track pattern appears on Class Reports.
   One class per column shape on desktop; all of them collapse to the same
   stacked form on a phone: first cell full width, the rest flowing beneath. */
.cx-datarow { display: grid; gap: 12px; align-items: center; }
.cx-datarow.c4  { grid-template-columns: 80px minmax(0,1fr) 110px auto; }
.cx-datarow.c5  { grid-template-columns: 80px minmax(0,1fr) 110px minmax(0,1fr) auto; }
.cx-datarow.c5b { grid-template-columns: 80px minmax(0,1fr) 80px minmax(0,1fr) 100px; }
.cx-datarow.r5  { grid-template-columns: 60px minmax(0,1fr) 110px 130px 100px; }
.cx-datarow > * { min-width: 0; }

/* A row of equal stat tiles (Class Reports header). */
.cx-statrow { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }

@media (max-width: 760px) {
  .cx-datarow, .cx-datarow.c4, .cx-datarow.c5, .cx-datarow.c5b, .cx-datarow.r5 {
    display: flex; flex-wrap: wrap; align-items: baseline;
    column-gap: 10px; row-gap: 4px;
  }
  /* The roll/id chip stays inline with the name; everything after wraps. */
  .cx-datarow > :nth-child(2) { flex: 1 1 60%; min-width: 0; }
  .cx-datarow > :nth-child(n + 3) { flex: 0 0 auto; font-size: 11px; text-align: left !important; }
  .cx-statrow { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 420px) {
  .cx-statrow { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Teacher roster rows ------------------------------------------------
   name · username (180) · role (80) · scopes (110) · session (80) — about
   450px of fixed track. In a ~334px card on a phone the trailing columns
   simply painted OUTSIDE the card, over the page background, because
   nothing in the chain clipped them.

   (Worth noting for future audits: a document-level scrollWidth check does
   NOT catch this. The pane above these rows carried `overflow: hidden`, so
   the spill was clipped at the pane and never widened the document — it just
   rendered on top of the background. Row-level measurement is what finds it.) */
.cx-tchrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 180px 80px 110px 80px;
}
.cx-tchqueue {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 140px 90px auto;
}
.cx-tchqueue.reset { grid-template-columns: minmax(0, 1fr) 120px auto; }
.cx-tchrow > *, .cx-tchqueue > * { min-width: 0; }

@media (max-width: 760px) {
  /* Identity on the first line; the four metadata cells flow and wrap
     beneath it as one run, same pattern as the activity rows. */
  .cx-tchrow, .cx-tchqueue, .cx-tchqueue.reset {
    display: flex; flex-wrap: wrap; align-items: baseline;
    column-gap: 10px; row-gap: 4px;
  }
  .cx-tchrow > :first-child,
  .cx-tchqueue > :first-child { flex: 1 1 100%; min-width: 0; }
  .cx-tchrow > :not(:first-child),
  .cx-tchqueue > :not(:first-child) {
    flex: 0 0 auto; text-align: left !important; font-size: 11px;
  }
}

/* ---- Student detail pane ------------------------------------------------
   Desktop: a column that TRACKS the viewport while the roster scrolls past.
   Phone:   a bottom sheet, because at ≤1000px the split collapses to one
            column and the pane would otherwise land below hundreds of roster rows,
            i.e. somewhere nobody will ever scroll to. */
.cx-roster-detail {
  border-left: 1px solid var(--line);
  background: var(--surface);
  display: flex; flex-direction: column;
  min-width: 0;
}
.cx-roster-scrim { display: none; }
.cx-sheet-grip { display: none; }

@media (min-width: 1001px) {
  .cx-roster-detail {
    position: sticky;
    top: calc(var(--topbar-h) + 16px);
    /* align-self is the load-bearing line. A grid item stretches to the row
       height by default, which leaves a sticky element nothing to travel
       within — it looks like sticky "doesn't work". Shrinking it to its
       content is what lets it stick. */
    align-self: start;
    max-height: calc(100vh - var(--topbar-h) - 32px);
    overflow-y: auto;
    overscroll-behavior: contain;
    border: 1px solid var(--line);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-card);
  }
}

@media (max-width: 1000px) {
  .cx-roster-split.has-detail { grid-template-columns: minmax(0, 1fr); }

  .cx-roster-scrim {
    display: block;
    position: fixed; inset: 0; z-index: 245;
    background: color-mix(in srgb, var(--ink-0) 34%, transparent);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
    animation: cx-fade-in 0.18s ease-out;
  }
  .cx-roster-detail {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto;
    z-index: 250;
    max-height: 88vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    border: none;
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -14px 44px color-mix(in srgb, var(--ink-0) 24%, transparent);
    padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
    animation: cx-sheet-up 0.26s cubic-bezier(.2,.85,.3,1);
  }
  .cx-sheet-grip {
    display: block; flex-shrink: 0;
    width: 40px; height: 4px; border-radius: 999px;
    background: var(--bg-4);
    margin: 10px auto 2px;
  }
  /* The sheet supplies its own top chrome, so the panel header loses the
     border that separated it from a column edge. */
  .cx-roster-detail-head { border-bottom: 1px solid var(--line-soft) !important; }
}

@keyframes cx-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes cx-sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) {
  .cx-roster-detail, .cx-roster-scrim { animation: none; }
}
@media (max-width: 760px) {
  /* Two columns, three rows. Source order is:
     1 roll · 2 identity · 3 class · 4 type-pill · 5 login · 6 activity. */
  .cx-roster-row {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: 4px !important;
    column-gap: 10px !important;
    align-items: center !important;
  }
  .cx-roster-row > :nth-child(1) { grid-column: 1; grid-row: 1; }   /* roll  */
  .cx-roster-row > :nth-child(4) { grid-column: 2; grid-row: 1;     /* type  */
                                   justify-self: end; }
  .cx-roster-row > :nth-child(2) { grid-column: 1 / -1; grid-row: 2; } /* name + email */
  .cx-roster-row > :nth-child(3) { grid-column: 1; grid-row: 3; }   /* class */
  .cx-roster-row > :nth-child(5) { grid-column: 1; grid-row: 4; }   /* login */
  .cx-roster-row > :nth-child(6) { grid-column: 2; grid-row: 3 / span 2;
                                   text-align: right !important; align-self: center; }
}

/* ---- Staff attendance heat-map -----------------------------------------
   Desktop: avatar · name (210px) · the day strip · the figure.
   The strip needs ~820px, so on a phone the whole row used to live inside a
   horizontal scroller and got sliced off at the card edge — a hard vertical
   cut with no hint that anything continued past it.

   Below 760px the row stops being a row: identity on the first line, the day
   strip WRAPPING across the full width beneath it, and the figure pinned to
   the right of the name. Nothing overflows, so nothing is cut. */
.cx-attgrid { min-width: 820px; }
.cx-attrow { display: flex; align-items: center; gap: 16px; }
.cx-attwho { width: 210px; min-width: 0; flex-shrink: 0; }
.cx-attcells { flex: 1; display: flex; align-items: center; gap: 3px; min-width: 0; flex-wrap: wrap; }
.cx-attstat {
  width: 96px; text-align: right; font-size: 13.5px; font-weight: 700;
  color: var(--ink-0); flex-shrink: 0;
}

@media (max-width: 760px) {
  /* No inner min-width: nothing to scroll, so the scroll affordance is
     switched off outright rather than left to the local-gradient trick to
     hide by coincidence. */
  .cx-attgrid { min-width: 0; }
  .cx-attscroll { overflow-x: visible; background: none; }

  .cx-attrow {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 12px; row-gap: 8px;
  }
  /* Explicit placement for all four. Auto-placement put the figure on a
     third line of its own, because the full-width strip above it consumed
     row 2 and the next free cell was row 3. */
  .cx-attrow > .cx-av { grid-column: 1; grid-row: 1; }
  .cx-attwho  { grid-column: 2; grid-row: 1; width: auto; }
  .cx-attstat { grid-column: 3; grid-row: 1; width: auto; }
  /* The strip drops to its own full-width line and wraps there. */
  .cx-attcells {
    grid-column: 1 / -1; grid-row: 2;
    gap: 4px;
  }
  /* Slightly larger cells now that they have the full width to wrap into —
     a 31-day month lands on two comfortable lines instead of one cramped. */
  .cx-attcells .cx-heat { width: 15px; height: 15px; }
}

/* ---- Floating chrome surface ("lg") -----------------------------------
   Used by the radial nav widget and other floating controls. A flat, opaque
   surface; .lg.dark is the filled primary variant. */
.lg {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  box-shadow: var(--shadow-pop);
}
.lg > * { position: relative; z-index: 1; }
.lg.dark {
  background: var(--primary);
  color: var(--primary-ink);
  border-color: transparent;
}

/* ---- Radial navigation widget (phones + installed PWA) -----------------
   A single floating circle instead of a six-slot bar. Two ways in, sharing
   one pointer pipeline (see CockpitRadialNav in shell.jsx):

     · tap        → the arc opens and stays open; tap a section
     · press-drag → the arc opens under the finger, the section nearest the
                    drag angle highlights, releasing selects it

   Why a widget and not a bar: six labelled slots at 390px were already
   ellipsising ("Academi…") and dropped to icons-only below 360px. An arc
   gives every section a full label at any width, and returns the ~66px the
   bar was permanently taking off the bottom of every screen.

   Hidden by default, revealed under 760px, so desktop never pays for it. */
.cx-radialnav { display: none; }

@media (max-width: 760px) {
  .cx-radialnav {
    display: block;
    position: fixed;
    left: 0; right: 0;
    bottom: calc(14px + env(safe-area-inset-bottom, 0px));
    height: 0;                 /* the children are absolutely placed */
    z-index: 240;
    pointer-events: none;      /* only the button and items are hit-testable */
  }

  /* The trigger — bottom-CENTRE. Centred with left:50% + translateX(-50%),
     which means EVERY other transform on this element must re-state that
     translate or the button jumps to the right on press/open. */
  .cx-fab {
    position: absolute; left: 50%; right: auto; bottom: 0;
    transform: translateX(-50%);
    width: 60px; height: 60px; border-radius: 50%;
    display: grid; place-items: center;
    color: var(--on-solid);
    pointer-events: auto;
    touch-action: none;        /* we own the drag; stop the page scrolling */
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.2s cubic-bezier(.2,.8,.3,1),
                background 0.18s, backdrop-filter 0.18s;
  }
  .cx-fab:active { transform: translateX(-50%) scale(0.93); }
  /* Reading downward: the trigger recedes so it stops covering the row under
     it. Scrolling back up restores it immediately. Scale stops at 0.72 (43px)
     rather than going smaller — a transform shrinks the hit area with the
     visual, and below this it stops being reliably tappable. */
  .cx-radialnav.mini .cx-fab {
    transform: translateX(-50%) scale(0.72);
    opacity: 0.62;
  }
  .cx-radialnav.mini .cx-fab:active { transform: translateX(-50%) scale(0.68); }
  /* Glass "flexes" under pressure: the blur deepens as the control is held. */
  .cx-fab.open {
    transform: translateX(-50%) rotate(45deg);
    -webkit-backdrop-filter: blur(36px) saturate(220%);
    backdrop-filter: blur(36px) saturate(220%);
  }
  .cx-fab.open .cx-fab-face { transform: rotate(-45deg); }
  .cx-fab-face { display: grid; place-items: center; transition: transform 0.2s; }
  /* Dimmer behind the open arc — also the tap-to-close target. */
  .cx-radial-veil {
    position: fixed; inset: 0;
    background: color-mix(in srgb, var(--ink-0) 22%, transparent);
    -webkit-backdrop-filter: blur(3px) saturate(115%);
    backdrop-filter: blur(3px) saturate(115%);
    pointer-events: auto;
    animation: cx-veil-in 0.18s ease-out;
  }
  @keyframes cx-veil-in { from { opacity: 0; } to { opacity: 1; } }

  /* Arc items, centred on the trigger and displaced by --x/--y (written per
     item by the component). margin-left/bottom are computed so an item's
     centre coincides with the trigger's centre at rest:
       margin-left = −56/2 = −28     bottom = 30 − 28 = 2 */
  .cx-radial-item {
    position: absolute; left: 50%; right: auto; bottom: 2px;
    margin-left: -28px;
    width: 56px; height: 56px;
    border-radius: 50%;
    color: var(--ink-1);
    display: grid; place-items: center; align-content: center;
    gap: 1px;
    pointer-events: auto;
    transform: translate(var(--x), var(--y)) scale(1);
    transition: transform 0.26s cubic-bezier(.2,.9,.3,1.15),
                opacity 0.18s, background 0.14s, color 0.14s, box-shadow 0.18s;
  }
  /* Denser than the base material: these sit over a dimmed veil, and at the
     base 0.44 fill the 8.5px labels dropped below a comfortable contrast. */
  .cx-radial-item { background: color-mix(in srgb, var(--surface) 72%, transparent); }
  .cx-radial-item .cx-radial-label {
    font-size: 8.5px; font-weight: 700; letter-spacing: -0.15px;
    max-width: 52px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* Collapsed: every item sits under the trigger, invisible. */
  .cx-radialnav:not(.open) .cx-radial-item {
    transform: translate(0, 0) scale(0.4);
    opacity: 0; pointer-events: none;
  }
  .cx-radial-item.current { color: var(--ink-0); }
  .cx-radial-item.current .cx-radial-label { text-decoration: underline; text-underline-offset: 2px; }
  /* Armed by the drag. The glass thickens and lifts, which is the feedback
     that a release right now will commit to this section. */
  .cx-radial-item.armed {
    background: color-mix(in srgb, var(--ink-0) 80%, transparent);
    border-color: color-mix(in srgb, var(--surface) 28%, transparent);
    color: var(--on-solid);
    transform: translate(var(--x), var(--y)) scale(1.18);
    box-shadow:
      inset 0 1px 1px color-mix(in srgb, var(--surface) 34%, transparent),
      0 14px 34px color-mix(in srgb, var(--ink-0) 38%, transparent);
  }

  /* Content clears the trigger; far less than the old bar needed. */
  .cx-page,
  .content > div:not(.cx-page) {
    padding-bottom: calc(94px + env(safe-area-inset-bottom, 0px)) !important;
  }

  /* The notifications dropdown. As an absolutely-positioned 380px panel it
     was wider than a 390px screen once the bell's offset was added, so it
     hung off the edge and clipped its own text. Pinned to the viewport
     instead, with the inline width/top overridden. */
  .cx-notifpanel {
    position: fixed !important;
    top: calc(58px + env(safe-area-inset-top, 0px)) !important;
    left: 12px !important; right: 12px !important;
    width: auto !important;
    max-height: calc(100vh - 150px - env(safe-area-inset-top, 0px)) !important;
    border-radius: 14px !important;
    overscroll-behavior: contain;   /* stop the page scrolling underneath */
  }
  /* A notification title is a full sentence; it must wrap, not truncate. */
  .cx-notifpanel [style*="font-size: 12px"] { overflow-wrap: anywhere; }
}

@media (prefers-reduced-motion: reduce) {
  .cx-fab, .cx-radial-item, .cx-fab-face { transition: none; }
  .cx-radial-veil { animation: none; }
}

/* ============ Legacy shell bits (kept for screens that still use them) ============ */
/* In-page toolbar with breadcrumbs (Paper Evaluation). */
.topbar {
  min-height: 52px;
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; flex-wrap: wrap;
  padding: 8px 24px;
  gap: 12px;
  background: var(--surface);
}
.crumbs { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--ink-3); min-width: 0; }
.crumbs .sep { color: var(--ink-4); }
.crumbs .here { color: var(--ink-0); font-weight: 500; }
.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

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

/* ============ Buttons ============
   .btn            outline (hairline on surface) — the default
   .btn.primary    solid --primary              .btn.success  solid green
   .btn.ghost      no chrome until hover        .btn.danger   red text, red hairline
   .btn.sm / .xs / .lg   sizes (28 / 24 / 36; default 32)
   .btn.icon-only  square icon button           .btn.round    fully round  */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 32px; padding: 5px 12px;
  border-radius: var(--radius-ctl);
  font-size: 13px; font-weight: 500; line-height: 1.2;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink-1);
  transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.06s;
  white-space: nowrap;
  text-decoration: none;
}
.btn svg { flex-shrink: 0; }
.btn:hover { background: var(--bg-2); color: var(--ink-0); }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-bg); }
.btn.outline { background: var(--surface); border-color: var(--line-strong); }
/* --primary, NOT --accent: the accent is user-tintable from the Tweaks panel
   and a pale tint would leave this button unreadable. */
.btn.primary {
  background: var(--primary); color: var(--primary-ink);
  border-color: transparent;
}
.btn.primary:hover { background: var(--primary-hover); color: var(--primary-ink); }
.btn.success { background: var(--green-solid); color: var(--on-solid); border-color: transparent; }
.btn.success:hover { background: color-mix(in srgb, var(--green-solid) 88%, var(--ink-0)); color: var(--on-solid); }
.btn.ghost { background: transparent; border-color: transparent; color: var(--ink-2); }
.btn.ghost:hover { background: var(--bg-3); color: var(--ink-0); }
.card .btn.ghost:hover, .cx-card .btn.ghost:hover, .ui-modal .btn.ghost:hover { background: var(--bg-2); }
.btn.danger { color: var(--red); border-color: color-mix(in srgb, var(--red-solid) 30%, transparent); background: var(--surface); }
.btn.danger:hover { background: var(--red-bg); color: var(--red); }
.btn.sm { min-height: 28px; padding: 3px 10px; font-size: 12.5px; gap: 5px; }
.btn.xs { min-height: 24px; padding: 2px 8px; font-size: 12px; gap: 4px; border-radius: 6px; }
.btn.lg { min-height: 36px; padding: 6px 16px; font-size: 13.5px; }
.btn.icon-only { padding: 0 7px; min-width: 32px; }
.btn.sm.icon-only { min-width: 28px; padding: 0 6px; }
.btn.round { border-radius: 999px; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* Group of buttons that touch (rare): <div class="btn-group">. */
.btn-group { display: inline-flex; }
.btn-group > .btn:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: -1px; }
.btn-group > .btn:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; }

/* ============ Form controls ============ */
.field { display: flex; flex-direction: column; gap: 6px; }
.label {
  font-size: 12.5px; color: var(--ink-2);
  font-weight: 500; letter-spacing: 0;
}
.hint { font-size: 12px; color: var(--ink-3); }
.input, .select, .textarea {
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-ctl);
  padding: 6px 10px;
  min-height: 34px;
  color: var(--ink-0);
  font-size: 13px;
  line-height: 1.35;
  outline: none;
  transition: border-color 0.12s, box-shadow 0.12s;
  width: 100%;
}
.input:hover, .select:hover, .textarea:hover { border-color: color-mix(in srgb, var(--ink-4) 60%, var(--line-strong)); }
.input::placeholder, .textarea::placeholder { color: var(--ink-4); }
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-bg);
}
.input:disabled, .select:disabled, .textarea:disabled { background: var(--bg-2); color: var(--ink-3); cursor: not-allowed; }
.textarea { resize: vertical; min-height: 72px; padding: 8px 10px; }

/* Native pickers follow the light theme. */
input[type="date"], input[type="time"], input[type="datetime-local"] { color-scheme: light; }
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: 0.55;
}
input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }
.select {
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' stroke='%238a8a93' stroke-width='1.5' fill='none' stroke-linecap='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 28px;
}
/* Search field with a leading icon: <div class="ui-search"><Icon/><input class="input"/></div> */
.ui-search { position: relative; display: flex; align-items: center; min-width: 0; }
.ui-search > svg { position: absolute; left: 10px; color: var(--ink-3); pointer-events: none; }
.ui-search > .input { padding-left: 32px; }

/* ============ Pills / badges ============ */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  min-height: 22px; padding: 1px 9px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1.3;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink-2);
  white-space: nowrap;
}
.pill .swatch { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.pill.amber { color: var(--amber); border-color: transparent; background: var(--amber-bg); }
.pill.green { color: var(--green); border-color: transparent; background: var(--green-bg); }
.pill.red   { color: var(--red);   border-color: transparent; background: var(--red-bg); }
.pill.blue  { color: var(--blue);  border-color: transparent; background: var(--blue-bg); }
.pill.violet{ color: var(--violet);border-color: transparent; background: var(--violet-bg); }
.pill.pink  { color: var(--pink);  border-color: transparent; background: var(--pink-bg); }
.pill.grey  { color: var(--ink-2); border-color: transparent; background: var(--bg-3); }
.pill.solid-amber { background: var(--amber-solid); color: var(--on-solid); border-color: transparent; font-weight: 600; }
.pill.solid-green { background: var(--green-solid); color: var(--on-solid); border-color: transparent; font-weight: 600; }
.pill.solid-red   { background: var(--red-solid);   color: var(--on-solid); border-color: transparent; font-weight: 600; }
.pill.solid-blue  { background: var(--blue-solid);  color: var(--on-solid); border-color: transparent; font-weight: 600; }

/* ============ Card ============ */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  min-width: 0;
}
.card-head {
  padding: 14px 20px;
  min-height: 56px;
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; gap: 10px;
}
/* Title-over-body cards read as one surface: no rule between them. A head
   followed directly by a table/list keeps its hairline. */
.card-head:has(+ .card-body) { border-bottom-color: transparent; padding-bottom: 4px; min-height: 0; padding-top: 18px; }
.card-title { color: var(--ink-0); font-weight: 600; font-size: 15px; letter-spacing: -0.1px; }
.card-body { padding: 16px 20px 20px; }
.card-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px;
  padding: 12px 20px; border-top: 1px solid var(--line);
}

/* ============ Tables ============
   ONE treatment for every table in the cockpit. `table.tests` is the original
   class (test library, dashboard lists); `table.cx-table` is the same thing
   under a name the newer screens use.

   Wrap a table in `.cx-tablecard` when it is not already inside a card. */
.cx-tablecard {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;      /* clips the header's square corners to the radius */
}
.cx-tablecard > .cx-tablecard-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  padding: 16px 20px;
  border-bottom: 1px solid var(--line);
}
.cx-tablecard-title { font-size: 16px; font-weight: 600; color: var(--ink-0); }
/* Horizontal scroll belongs to the wrapper, never the page. The layered
   backgrounds are the scroll-shadow trick: a soft shade appears only on the
   side that has more content. */
.cx-tablescroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  background:
    linear-gradient(to right, var(--surface) 32%, color-mix(in srgb, var(--surface) 0%, transparent)) left center,
    linear-gradient(to left,  var(--surface) 32%, color-mix(in srgb, var(--surface) 0%, transparent)) right center,
    radial-gradient(farthest-side at 0    50%, color-mix(in srgb, var(--ink-0) 13%, transparent), color-mix(in srgb, var(--ink-0) 0%, transparent)) left center,
    radial-gradient(farthest-side at 100% 50%, color-mix(in srgb, var(--ink-0) 13%, transparent), color-mix(in srgb, var(--ink-0) 0%, transparent)) right center;
  background-repeat: no-repeat;
  background-size: 36px 100%, 36px 100%, 15px 100%, 15px 100%;
  background-attachment: local, local, scroll, scroll;
}

table.tests, table.cx-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 13px;
}
table.tests thead th, table.cx-table thead th {
  text-align: left;
  font-weight: 500;
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-3);
  padding: 10px 16px;
  height: 40px;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  position: sticky; top: 0;
  z-index: 1;
  white-space: nowrap;
}
table.tests tbody td, table.cx-table tbody td {
  padding: 12px 16px;
  height: 48px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
  color: var(--ink-1);
}
/* The last row's rule doubles the card's own border. */
table.tests tbody tr:last-child td,
table.cx-table tbody tr:last-child td { border-bottom: none; }
table.tests tbody tr, table.cx-table tbody tr { transition: background 0.1s; }
table.tests tbody tr:hover, table.cx-table tbody tr:hover { background: var(--surface-hover); }

/* Row-level modifiers */
table.cx-table tbody tr.muted-row { opacity: 0.55; }
table.cx-table td.num, table.cx-table th.num,
table.tests td.num, table.tests th.num {
  text-align: right; font-variant-numeric: tabular-nums;
}
table.cx-table td.actions, table.cx-table th.actions {
  text-align: right; white-space: nowrap;
}
/* Action buttons sit quiet until the row is hovered. */
table.cx-table td.actions .btn { opacity: 0.8; }
table.cx-table tbody tr:hover td.actions .btn { opacity: 1; }
/* Primary cell: the name/title column. */
table.cx-table td .t-main, table.tests td .t-main { color: var(--ink-0); font-weight: 500; font-size: 13.5px; }
table.cx-table td .t-sub, table.tests td .t-sub  { color: var(--ink-3); font-size: 12px; margin-top: 1px; }

/* Compact variant for sub-tables nested inside a detail panel. */
table.cx-table.compact { font-size: 12.5px; }
table.cx-table.compact thead th { padding: 8px 10px; font-size: 10.5px; height: 32px; }
table.cx-table.compact tbody td { padding: 8px 10px; height: 38px; }

/* The "nothing here yet" panel that stands in for a table. */
.cx-emptycard {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 40px 24px;
  text-align: center;
  color: var(--ink-3);
  font-size: 13.5px;
}
.cx-emptycard strong { display: block; color: var(--ink-0); font-size: 15px; font-weight: 600; margin-bottom: 6px; }

/* misc */
.divider { height: 1px; background: var(--line); margin: 0; border: 0; }

.scanline-bg { background: var(--bg-2); }

/* progress bar */
.progress { height: 5px; background: var(--bg-3); border-radius: 999px; overflow: hidden; }
.progress > div { height: 100%; background: var(--blue-solid); border-radius: 999px; }
.progress.green > div { background: var(--green-solid); }

/* checkbox */
.chk {
  width: 15px; height: 15px;
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  background: var(--surface);
  display: inline-grid; place-items: center;
  flex-shrink: 0;
}
.chk.on { background: var(--accent); border-color: var(--accent); }
.chk.on::after {
  content: "";
  width: 7px; height: 4px;
  border-left: 1.5px solid var(--surface);
  border-bottom: 1.5px solid var(--surface);
  transform: rotate(-45deg) translate(0, -1px);
}

.muted { color: var(--ink-3); }
.dim { color: var(--ink-2); }


/* ==========================================================================
   Primitive layer
   --------------------------------------------------------------------------
   Classes for screens to build with (helpers in shell.jsx → window.AppUI).
     .ui-pagehead-*  page header: back link, avatar, title + status, meta, actions
     .ui-avatar      round initials        .ui-badge   status pill
     .ui-tabs        segmented tabs        .ui-statrow big-number columns
     .ui-details     label/value grid      .ui-row     hairline list row
     .ui-iconbtn     round icon button     .ui-modal   dialog shell
     .ui-toolbar     row of outline buttons under a header
     .ui-alert       inline tinted message .ui-section-label  uppercase label
   ========================================================================== */

/* ---- Page header ---- */
.ui-pagehead-wrap { margin-bottom: 24px; }
.ui-back {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 500; color: var(--ink-3);
  padding: 4px 6px 4px 0; margin-bottom: 14px; border-radius: 6px;
  transition: color 0.12s;
}
.ui-back:hover { color: var(--ink-0); }
.ui-pagehead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px 24px; flex-wrap: wrap;
}
.ui-pagehead-main { display: flex; align-items: center; gap: 18px; min-width: 0; flex: 1 1 320px; }
.ui-pagehead-titlerow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }
.ui-pagehead-title {
  margin: 0; font-size: 26px; font-weight: 600; letter-spacing: -0.5px;
  line-height: 1.2; color: var(--ink-0); overflow-wrap: anywhere;
}
.ui-pagehead-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 16px;
  margin-top: 6px; font-size: 13.5px; color: var(--ink-3);
}
.ui-pagehead-meta .mono, .ui-id { font-family: var(--font-mono); font-size: 12.5px; letter-spacing: 0.01em; color: var(--ink-3); }
.ui-id { white-space: nowrap; }
.ui-pagehead-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ui-toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 20px; }
.ui-section-label {
  font-size: 11px; font-weight: 500; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-3);
}

/* ---- Avatar ---- */
.ui-avatar {
  width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0;
  display: inline-grid; place-items: center;
  background: var(--bg-3); border: 1px solid var(--line-strong);
  color: var(--ink-2); font-size: 12.5px; font-weight: 500; letter-spacing: 0.03em;
  line-height: 1; user-select: none;
}
.ui-avatar.sm { width: 28px; height: 28px; font-size: 11px; }
.ui-avatar.lg { width: 44px; height: 44px; font-size: 14px; }
.ui-avatar.xl { width: 64px; height: 64px; font-size: 17px; }

/* ---- Status badge / pill ----
   .ui-badge.<variant>.<tone>
     variant: tint (default fill) · outline (header status, icon + text) · solid
     tone:    green amber red blue violet pink grey (none = neutral) */
.ui-badge {
  display: inline-flex; align-items: center; gap: 4px;
  min-height: 22px; padding: 1px 9px;
  border-radius: 999px; border: 1px solid transparent;
  font-size: 12px; font-weight: 500; line-height: 1.3;
  white-space: nowrap; font-variant-numeric: tabular-nums;
  background: var(--bg-3); color: var(--ink-2);
}
.ui-badge svg { width: 12px; height: 12px; flex-shrink: 0; }
.ui-badge.lg { min-height: 26px; padding: 2px 11px; font-size: 12.5px; }
.ui-badge.tint.green  { background: var(--green-bg);  color: var(--green); }
.ui-badge.tint.amber  { background: var(--amber-bg);  color: var(--amber); }
.ui-badge.tint.red    { background: var(--red-bg);    color: var(--red); }
.ui-badge.tint.blue   { background: var(--blue-bg);   color: var(--blue); }
.ui-badge.tint.violet { background: var(--violet-bg); color: var(--violet); }
.ui-badge.tint.pink   { background: var(--pink-bg);   color: var(--pink); }
.ui-badge.tint.grey   { background: var(--bg-3);      color: var(--ink-2); }
.ui-badge.outline { background: transparent; border-color: var(--line-strong); color: var(--ink-2); }
.ui-badge.outline.green  { border-color: color-mix(in srgb, var(--green-solid) 40%, transparent);  color: var(--green);  background: color-mix(in srgb, var(--green-solid) 6%, transparent); }
.ui-badge.outline.amber  { border-color: color-mix(in srgb, var(--amber-solid) 45%, transparent);  color: var(--amber);  background: color-mix(in srgb, var(--amber-solid) 6%, transparent); }
.ui-badge.outline.red    { border-color: color-mix(in srgb, var(--red-solid) 40%, transparent);    color: var(--red);    background: color-mix(in srgb, var(--red-solid) 5%, transparent); }
.ui-badge.outline.blue   { border-color: color-mix(in srgb, var(--blue-solid) 40%, transparent);   color: var(--blue);   background: color-mix(in srgb, var(--blue-solid) 5%, transparent); }
.ui-badge.outline.violet { border-color: color-mix(in srgb, var(--violet-solid) 40%, transparent); color: var(--violet); background: color-mix(in srgb, var(--violet-solid) 5%, transparent); }
.ui-badge.outline.pink   { border-color: color-mix(in srgb, var(--pink-solid) 40%, transparent);   color: var(--pink);   background: color-mix(in srgb, var(--pink-solid) 5%, transparent); }
.ui-badge.outline.grey   { color: var(--ink-3); }
.ui-badge.solid { color: var(--on-solid); background: var(--ink-2); font-weight: 600; }
.ui-badge.solid.green  { background: var(--green-solid); }
.ui-badge.solid.amber  { background: var(--amber-solid); }
.ui-badge.solid.red    { background: var(--red-solid); }
.ui-badge.solid.blue   { background: var(--blue-solid); }
.ui-badge.solid.violet { background: var(--violet-solid); }
.ui-badge.solid.pink   { background: var(--pink-solid); }
.ui-badge.solid.grey   { background: var(--grey-solid); color: var(--ink-1); }

/* ---- Segmented tabs ---- */
.ui-tabs, .seg {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 3px; background: var(--track);
  border: 0; border-radius: var(--radius-seg);
  max-width: 100%; overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.ui-tabs::-webkit-scrollbar, .seg::-webkit-scrollbar { display: none; }
.ui-tabs.full { display: flex; width: 100%; }
.ui-tabs button, .seg button {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  flex: 0 0 auto;
  min-height: 32px; padding: 0 14px;
  border-radius: 7px;
  font-size: 13.5px; font-weight: 500; color: var(--ink-3);
  white-space: nowrap;
  transition: background 0.12s, color 0.12s, box-shadow 0.12s;
}
.ui-tabs button:hover:not(:disabled), .seg button:hover:not(:disabled) { color: var(--ink-0); }
.ui-tabs button.active, .seg button.active {
  background: var(--surface); color: var(--ink-0); font-weight: 500;
  box-shadow: var(--shadow-raised);
}
.ui-tabs button:disabled, .seg button:disabled { opacity: 0.45; cursor: not-allowed; }
.ui-tab-count {
  font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums;
  padding: 0 6px; border-radius: 999px; background: color-mix(in srgb, var(--ink-0) 6%, transparent);
}
.ui-tabs button.active .ui-tab-count { background: var(--bg-3); }
/* Small segmented (inside card headers / dense toolbars). */
.ui-tabs.sm button, .seg button { min-height: 26px; padding: 0 10px; font-size: 12.5px; }
.ui-tabs.sm { padding: 2px; border-radius: 8px; }
.seg { padding: 2px; border-radius: 8px; }
.ui-tabs.sm button, .seg button { border-radius: 6px; }

/* ---- Stat row: big number over caption, equal columns ---- */
.ui-statrow {
  display: grid; grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr));
  gap: 12px; text-align: center;
  border-top: 1px solid var(--line); margin-top: 20px; padding-top: 18px;
}
.ui-statrow.flush { border-top: 0; margin-top: 0; padding-top: 0; }
.ui-statrow.left { text-align: left; }
.ui-stat { min-width: 0; }
.ui-stat-value {
  font-size: 22px; font-weight: 600; letter-spacing: -0.4px; line-height: 1.15;
  color: var(--ink-0); font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
.ui-stat-value.green { color: var(--green); }
.ui-stat-value.amber { color: var(--amber); }
.ui-stat-value.red   { color: var(--red); }
.ui-stat-value.blue  { color: var(--blue); }
.ui-stat-label { font-size: 12.5px; color: var(--ink-3); margin-top: 4px; }

/* ---- Detail grid: muted label over value ---- */
.ui-details {
  display: grid; grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr));
  gap: 18px 24px; margin: 0;
}
.ui-detail { min-width: 0; }
.ui-detail-label { font-size: 12.5px; color: var(--ink-3); margin: 0 0 3px; }
.ui-detail-value { font-size: 14px; color: var(--ink-0); margin: 0; overflow-wrap: anywhere; }
.ui-detail-value.mono { font-size: 13px; }

/* ---- List rows ---- */
.ui-list { display: flex; flex-direction: column; }
.ui-row {
  display: flex; align-items: center; gap: 14px;
  width: 100%; padding: 12px 0; text-align: left;
  border-bottom: 1px solid var(--line);
  min-width: 0; font: inherit; color: inherit;
}
.ui-row:last-child { border-bottom: 0; }
.ui-row.clickable { cursor: pointer; border-radius: 0; transition: background 0.12s; }
.ui-row.clickable:hover { background: var(--surface-hover); box-shadow: -10px 0 0 var(--surface-hover), 10px 0 0 var(--surface-hover); }
.ui-row-main { flex: 1; min-width: 0; }
.ui-row-title { font-size: 14px; font-weight: 500; color: var(--ink-0); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-row-sub { font-size: 12.5px; color: var(--ink-3); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ui-row-end { display: flex; align-items: center; gap: 16px; flex-shrink: 0; }
.ui-amount {
  font-size: 14px; font-weight: 500; color: var(--ink-0);
  font-variant-numeric: tabular-nums; text-align: right; min-width: 56px;
}

/* ---- Icon button (round ghost) ---- */
.ui-iconbtn {
  width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
  display: inline-grid; place-items: center;
  color: var(--ink-1); background: transparent; border: 1px solid transparent;
  transition: background 0.12s, color 0.12s;
}
.ui-iconbtn:hover { background: var(--bg-3); color: var(--ink-0); }
.ui-iconbtn.outline { border-color: var(--line-strong); background: var(--surface); }
.ui-iconbtn.outline:hover { background: var(--bg-2); }
.ui-iconbtn.sm { width: 28px; height: 28px; }
.ui-iconbtn:disabled { opacity: 0.45; cursor: not-allowed; }

/* ---- Inline alert ---- */
.ui-alert {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 9px 12px; border-radius: var(--radius-ctl);
  font-size: 13px; line-height: 1.4;
  background: var(--bg-2); color: var(--ink-2); border: 1px solid var(--line);
}
.ui-alert.red   { background: var(--red-bg);   color: var(--red);   border-color: color-mix(in srgb, var(--red-solid) 25%, transparent); }
.ui-alert.amber { background: var(--amber-bg); color: var(--amber); border-color: color-mix(in srgb, var(--amber-solid) 25%, transparent); }
.ui-alert.green { background: var(--green-bg); color: var(--green); border-color: color-mix(in srgb, var(--green-solid) 25%, transparent); }
.ui-alert.blue  { background: var(--blue-bg);  color: var(--blue);  border-color: color-mix(in srgb, var(--blue-solid) 25%, transparent); }

/* ---- Modal ---- */
.ui-scrim {
  position: fixed; inset: 0; z-index: 250;
  display: grid; place-items: center;
  padding: 24px;
  background: var(--overlay);
  animation: cx-fade-in 0.16s ease-out;
}
.ui-modal {
  width: min(560px, 100%); max-height: 88vh;
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-pop);
  overflow: hidden;
  animation: ui-modal-in 0.18s cubic-bezier(.2,.85,.3,1);
}
.ui-modal.lg { width: min(780px, 100%); }
.ui-modal.xl { width: min(920px, 100%); }
.ui-modal-head {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 18px 16px 14px 20px;
}
.ui-modal-icon {
  width: 32px; height: 32px; border-radius: var(--radius-ctl); flex-shrink: 0;
  display: grid; place-items: center;
  background: var(--bg-3); color: var(--ink-1);
}
.ui-modal-title { font-size: 16px; font-weight: 600; letter-spacing: -0.15px; color: var(--ink-0); line-height: 1.3; }
.ui-modal-sub { font-size: 12.5px; color: var(--ink-3); margin-top: 2px; }
.ui-modal-close { margin-left: auto; margin-top: -4px; }
.ui-modal-body { padding: 2px 20px 20px; overflow: auto; flex: 1; min-height: 0; }
.ui-modal-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 0 20px 14px; border-bottom: 1px solid var(--line);
}
.ui-modal-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap;
  padding: 12px 20px;
  border-top: 1px solid var(--line);
  background: var(--bg-2);
}
@keyframes ui-modal-in { from { opacity: 0; transform: translateY(6px) scale(0.985); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .ui-scrim, .ui-modal { animation: none; } }


/* answer-card grid */
.cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
  gap: 14px;
}

.answer-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  display: flex; flex-direction: column;
  overflow: hidden;
  box-shadow: var(--shadow-card);
  transition: border-color 0.15s, transform 0.15s;
}
.answer-card:hover { border-color: var(--line-strong); }
.answer-card.pending { border-color: color-mix(in srgb, var(--amber-solid) 45%, transparent); }
.answer-card.confirmed { border-color: color-mix(in srgb, var(--green-solid) 45%, transparent); background: var(--surface); }
.answer-card.overridden { border-color: color-mix(in srgb, var(--blue-solid) 45%, transparent); background: var(--surface); }
.answer-card.failed { border-color: color-mix(in srgb, var(--red-solid) 45%, transparent); }
.answer-card.flagged { border-color: color-mix(in srgb, var(--amber-solid) 70%, transparent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--amber-solid) 12%, transparent); }

.answer-card .card-top {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line-soft);
}
.answer-card .student-name { color: var(--ink-0); font-weight: 600; font-size: 13px; }
.answer-card .roll { font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); }

/* Scanned answer crop — deliberately still "paper", on any theme. */
.crop {
  position: relative;
  background: var(--paper);
  color: var(--ink-0);
  font-family: var(--font-hand);
  padding: 16px;
  min-height: 130px;
  font-size: 15px;
  line-height: 1.4;
  border-bottom: 1px solid var(--line-soft);
}
.crop .corner {
  position: absolute; top: 6px; right: 8px;
  font-size: 10px;
  color: var(--ink-3);
  font-family: var(--font-mono);
}
.crop.bn { font-family: var(--font-sans); font-size: 17px; }

.transcription {
  padding: 10px 12px;
  font-size: 12px;
  color: var(--ink-2);
  line-height: 1.5;
  border-bottom: 1px solid var(--line-soft);
  background: var(--bg-2);
  position: relative;
}
.transcription .ttl {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-3);
  margin-bottom: 4px;
  display: flex; align-items: center; gap: 6px;
}

.criteria-list { padding: 10px 12px; border-bottom: 1px solid var(--line-soft); }
.criterion {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 4px 0;
  font-size: 11.5px;
  color: var(--ink-2);
}
.criterion .ck {
  width: 15px; height: 15px; flex-shrink: 0; margin-top: 1px;
  border-radius: 4px; display: grid; place-items: center;
  font-size: 9px;
}
.criterion .ck.match { background: var(--green-bg); color: var(--green); border: 1px solid color-mix(in srgb, var(--green-solid) 35%, transparent); }
.criterion .ck.partial { background: var(--amber-bg); color: var(--amber); border: 1px solid color-mix(in srgb, var(--amber-solid) 35%, transparent); }
.criterion .ck.miss { background: var(--bg-2); color: var(--ink-3); border: 1px solid var(--line-strong); }
.criterion .marks { margin-left: auto; font-family: var(--font-mono); font-size: 11px; color: var(--ink-3); }
.criterion .marks.has { color: var(--green); }

.card-bottom { padding: 10px 12px; display: flex; align-items: center; gap: 8px; }

.score-display {
  display: flex; align-items: baseline; gap: 2px;
  font-family: var(--font-mono);
  font-size: 17px;
  font-weight: 600;
  color: var(--ink-0);
}
.score-display .total { font-size: 11px; color: var(--ink-3); }
.score-display.green { color: var(--green); }
.score-display.amber { color: var(--amber); }
.score-display.red { color: var(--red); }

.confidence-dots { display: inline-flex; gap: 2px; align-items: center; }
.confidence-dots .d { width: 5px; height: 5px; border-radius: 50%; background: var(--bg-4); }
.confidence-dots .d.on.high { background: var(--green-solid); }
.confidence-dots .d.on.mid { background: var(--amber-solid); }
.confidence-dots .d.on.low { background: var(--red-solid); }

/* layout helpers */
.row { display: flex; align-items: center; gap: 8px; }
.col { display: flex; flex-direction: column; gap: 8px; }
.flex1 { flex: 1; min-width: 0; }
.spacer { flex: 1; }

/* PDF mock — a paper page on a neutral mat, so the scan is the brightest thing. */
.pdf-viewer {
  background: var(--grey-solid);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-card);
  padding: 12px;
  position: relative;
  min-height: 400px;
  overflow: hidden;
}
.pdf-page {
  background: var(--paper);
  color: var(--ink-0);
  border-radius: 4px;
  padding: 24px 28px;
  font-family: var(--font-hand);
  font-size: 16px;
  line-height: 1.7;
  min-height: 440px;
  box-shadow: 0 10px 30px color-mix(in srgb, var(--ink-0) 18%, transparent);
  position: relative;
}
.pdf-page .header-line {
  display: flex; justify-content: space-between;
  font-family: var(--font-sans);
  font-size: 11px; color: var(--ink-3);
  border-bottom: 1px solid var(--line-strong);
  padding-bottom: 6px;
  margin-bottom: 12px;
}
.bbox {
  position: absolute;
  border: 2px solid var(--blue-solid);
  border-radius: 4px;
  background: color-mix(in srgb, var(--blue-solid) 10%, transparent);
  pointer-events: none;
}
.bbox.confirmed { border-color: var(--green-solid); background: color-mix(in srgb, var(--green-solid) 10%, transparent); }
.bbox.active { border-color: var(--pink-solid); box-shadow: 0 0 0 2px color-mix(in srgb, var(--pink-solid) 25%, transparent); }
.bbox-label {
  position: absolute;
  top: -10px; left: -2px;
  background: var(--blue-solid); color: var(--on-solid);
  font-size: 10px; font-weight: 600;
  padding: 1px 6px; border-radius: 4px;
  font-family: var(--font-mono);
}
.bbox.confirmed .bbox-label { background: var(--green-solid); color: var(--on-solid); }
.bbox.active .bbox-label { background: var(--pink-solid); color: var(--on-solid); }

/* ==========================================================================
   Responsive + input-mode overrides — LAST, so they beat the base rules above.
   ========================================================================== */

/* ---- Responsive: collapse the multi-column grids before they crush ---- */
@media (max-width: 1180px) {
  .cx-grid.cols-3, .cx-grid.cols-4 { grid-template-columns: repeat(2, 1fr) !important; }
  .cx-grid.split, .cx-grid.split-wide, .cx-grid.split-narrow { grid-template-columns: 1fr !important; }
}

/* Sidebar → drawer. */
@media (max-width: 1023px) {
  .app > .nv-sidebar { display: none; }
  .nv-topbar .nv-menu-btn { display: grid; margin-left: -8px; }
  .nv-topbar .nv-topbar-mark { display: grid; place-items: center; }
  .nv-topbar { padding-left: calc(16px + env(safe-area-inset-left, 0px)); padding-right: calc(16px + env(safe-area-inset-right, 0px)); }
  .cx-subnavwrap {
    display: block;
    padding: 12px calc(24px + env(safe-area-inset-right, 0px)) 0 calc(24px + env(safe-area-inset-left, 0px));
  }
  .cx-page { padding: 24px 24px 48px; }
}

@media (max-width: 760px) {
  .nv-topbar { min-height: calc(52px + env(safe-area-inset-top, 0px)); gap: 8px; }
  .nv-crumbs { font-size: 13.5px; }
  .nv-crumbs > span:not(.here) { display: none; }
  .nv-crumbs .nv-crumb-sep { display: none; }
  .cx-subnavwrap {
    padding: 10px calc(12px + env(safe-area-inset-right, 0px)) 0 calc(12px + env(safe-area-inset-left, 0px));
  }
  .cx-page { padding: 16px 12px 0; }
  .cx-page-head { margin-bottom: 18px; gap: 14px; }
  .cx-eyebrow, .ui-eyebrow { margin-bottom: 5px; font-size: 11px; }
  /* Screens that lay themselves out (rosters) rather than using .cx-page. */
  .cx-roster-split > div:first-child { padding: 12px 14px !important; }
  .cx-grid.cols-3, .cx-grid.cols-4 { grid-template-columns: 1fr !important; }
  .cx-grid { gap: 14px; }

  .cx-h1, .ui-pagehead-title { font-size: 22px; letter-spacing: -0.4px; }
  .cx-h2 { font-size: 18px; }
  .ui-pagehead-main { gap: 14px; flex-basis: 100%; }
  .ui-avatar.xl { width: 52px; height: 52px; font-size: 15px; }
  .ui-pagehead-actions { width: 100%; }
  .ui-pagehead-meta { gap: 2px 12px; font-size: 13px; }
  .ui-toolbar { margin-top: 16px; }
  .ui-details { --cols: 2 !important; gap: 14px 16px; }
  .cx-card { padding: 18px; }
  .cx-card.tight { padding: 16px; }
  .cx-stat-value { font-size: 24px; }
  .cx-stat-value.sm { font-size: 20px; }
  .cx-stat { gap: 12px; }
  .cx-stat-label { max-width: none; }

  /* A stat card's footer is a label and a pill; side by side at 390px they
     collide, so the pill drops to its own line. */
  .cx-stat-foot { flex-direction: column; align-items: flex-start; gap: 8px; }

  /* Full-width primary actions read as buttons on a phone. */
  .cx-page-head .cx-btn-dark { width: 100%; justify-content: center; }
  .cx-seg, .ui-tabs { width: 100%; }
  .cx-seg button, .ui-tabs:not(.sm) button { flex: 1 0 auto; }

  /* Tables stay tabular and scroll inside their card. */
  table.cx-table thead th, table.cx-table tbody td { padding: 10px 12px; }

  /* Card headers carry filters, a search box and an action; they must wrap. */
  .card-head { flex-wrap: wrap; row-gap: 8px; padding: 12px 16px; }
  .card-body { padding: 14px 16px 16px; }
  .card-head .input, .card-head .select { min-width: 0; }

  .cx-emptycard { padding: 30px 18px; }
  .ui-scrim { padding: 12px; align-items: end; }
  .ui-modal { max-height: 92vh; border-radius: var(--radius-card) var(--radius-card) 10px 10px; }

  /* The A4 answer-key preview shrinks to the screen. */
  .ak-a4 { padding: 18px 16px !important; font-size: 12px !important; }
}
@media (max-width: 420px) {
  .ui-details { --cols: 1 !important; }
  .ui-statrow { gap: 8px; }
  .ui-stat-value { font-size: 19px; }
}

@media print {
  /* Paper is the one place the A4 sheet must keep its true width, and the
     app chrome must not appear at all. */
  .ak-a4 { width: 210mm !important; max-width: none !important; padding: 16mm 14mm !important; }
  .cx-radialnav, .nv-sidebar, .nv-topbar, .cx-subnavwrap, .cx-subnav { display: none !important; }
  body { background: var(--surface) !important; }
}

/* ---- Touch input: enforce a real hit area regardless of visual size ----
   The audit found 173 sub-32px targets on one screen alone. Rather than
   resize every button, guarantee the hit area on pointers that need it.
   `pointer: coarse` scopes this to touch, so mouse users keep the compact
   density the dense screens were designed for. */
@media (pointer: coarse) {
  .btn, .cx-pill, .cx-subnav-item, .seg button, .cx-seg button, .cx-round-btn,
  .ui-tabs button, .nv-nav-item, .ui-menu-item {
    min-height: 36px;
  }
  /* Native form controls default to ~28px here, which is under the touch
     guideline and awkward to hit while holding a phone one-handed.
     16px on inputs also stops iOS Safari zooming the page on focus. */
  .input, .select, .textarea { min-height: 40px; }
  .input, .select { font-size: 16px; }
  .btn.sm, .pill { min-height: 32px; }
  /* Width matters as much as height: the test library rows carry icon-only
     buttons that were 19–25px across even at a 32px height.
     `flex-shrink: 0` is the other half — min-width alone still let a flex
     parent squeeze a text button ("Admit in Ledger ↗") to 33px and clip it. */
  .btn, .btn.sm {
    padding-inline: 12px; min-width: 34px;
    justify-content: center; flex-shrink: 0;
  }
  /* Anything genuinely small (icon-only affordances, chips in dense grids)
     gets an invisible expanded hit area instead of a bigger box. */
  .btn.icon-only, .chk { position: relative; }
  .btn.icon-only::after, .chk::after {
    content: ""; position: absolute;
    top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 44px; height: 44px;
  }
}

/* ---- Small-phone type floor -------------------------------------------
   Screens built for a desktop table drop to 10px and below, which is not
   readable on a handset. This lifts the floor without touching the
   deliberate 11px metadata sizes. */
@media (max-width: 560px) {
  .content { font-size: 13px; }
  .content [style*="font-size: 9"], .content [style*="fontSize: 9"] { font-size: 11px !important; }
  .content [style*="font-size: 10"], .content [style*="fontSize: 10"] { font-size: 11.5px !important; }
  .mono { font-size: inherit; }
  /* Long unbroken strings (emails, usernames, IDs) are the usual cause of a
     table forcing the page wider than the screen. */
  table.cx-table td, table.tests td, .cx-row, .cx-tile { overflow-wrap: anywhere; }
  /* …but a table that can scroll sideways should scroll, not collapse its
     columns to one letter per line. */
  .cx-tablescroll table.cx-table td, .cx-tablescroll table.tests td { overflow-wrap: normal; }
}


/* ---- Login ---- */
.ui-login {
  position: fixed; inset: 0; overflow-y: auto;
  display: grid; place-items: center;
  background: var(--bg-0); padding: 16px;
}
.ui-login-card {
  width: min(380px, 100%);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 28px;
}
.ui-login-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 24px; min-width: 0; }
.ui-login-title { margin: 0; font-size: 22px; font-weight: 600; letter-spacing: -0.4px; color: var(--ink-0); }
.ui-login-sub { margin: 4px 0 20px; font-size: 13px; color: var(--ink-3); }
.ui-login-foot { margin: 16px 0 0; font-size: 12px; color: var(--ink-3); text-align: center; }
@media (max-width: 420px) { .ui-login-card { padding: 22px 18px; } }


/* Teacher rows also carry .ui-row (display:flex, declared later), so the
   grid / phone-flex switch is restated here at matching specificity. Kept
   out of inline styles so the 760px breakpoint can still win. */
.ui-row.cx-tchrow, .ui-row.cx-tchqueue { display: grid; }
@media (max-width: 760px) {
  .ui-row.cx-tchrow, .ui-row.cx-tchqueue { display: flex; }
}

/* Online tests: type picker beside the paste box, stacked on a phone. */
.ot-split { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 20px; }
.ot-split-aside { position: sticky; top: 12px; align-self: start; }
.ot-2col { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }
@media (max-width: 760px) {
  .ot-split, .ot-2col { grid-template-columns: minmax(0, 1fr); }
  .ot-split-aside { position: static; }
}

/* Phone: a modal head with pills/counts keeps the title readable — the
   extras drop to their own line under it, the close button stays top-right. */
@media (max-width: 560px) {
  .ui-modal-head { flex-wrap: wrap; }
  .ui-modal-head > .ui-modal-titleblock { flex: 1 1 180px !important; order: 0; }
  .ui-modal-head > .ui-modal-close { order: 2; }
  .ui-modal-head > .ui-modal-extra { order: 3; flex: 1 1 100%; justify-content: flex-start !important; }
}

/* The phone section FAB must not float over an open modal's footer. */
body:has(.ui-scrim) .cx-radialnav { display: none; }
