/* ===========================================================================
   TEACHER PORTAL — design system
   ---------------------------------------------------------------------------
   candl's navy for structure, with the accent pulled toward the Mechina
   logo's teal rather than candl's bright blue — the blue fought the logo;
   the teal sits beside it. Maroon (also from the logo) appears exactly once,
   on the rebbi-and-teacher badge. Flat surfaces, hairline rules, one radius.

   No webfont from a CDN, deliberately: candl's TODO records third-party
   fetches failing silently on the critical path. Inter is named first so a
   self-hosted copy drops in later with no other change.
   =========================================================================== */

/* ===========================================================================
   THEMES
   ---------------------------------------------------------------------------
   One set of markup, four token sets. `classic` is the design as it shipped
   and is deliberately unchanged — the depth tokens resolve to almost nothing
   there, so choosing it gives back exactly the flat, dense look rather than a
   toned-down version of something else.

   Fonts are SELF-HOSTED. candl's TODO records third-party font requests
   failing silently on the critical path, and this is a page a rebbi opens
   between sedarim. A browser only downloads a face a rule actually uses, so
   carrying three costs nothing to whichever theme is not using them.
   =========================================================================== */
@font-face {
  font-family: 'Plus Jakarta Sans'; src: url('fonts/plus-jakarta-sans.woff2') format('woff2');
  font-weight: 400 800; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Manrope'; src: url('fonts/manrope.woff2') format('woff2');
  font-weight: 400 800; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Instrument Serif'; src: url('fonts/instrument-serif.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  --navy:    #0e2743;
  --teal:    #0e6e69;
  --teal-lo: #0a5a56;
  --maroon:  #7d1f2e;
  --page:    #f5f7f6;
  --surface: #ffffff;
  --hover:   #edf1f0;
  --text:    #101d1c;
  --muted:   #5a6a68;
  --rule:    #dfe5e3;
  --rule-hi: #c7d1ce;
  --ok:      #0e6e69;
  --bad:     #a4341c;

  --sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --display: 'Poppins', 'Century Gothic', Futura, system-ui, sans-serif;
  --display-weight: 600;
  --display-tracking: -0.01em;

  --r: 8px;
  --r-lg: 8px;
  --tap: 44px;

  /* Depth. classic keeps these hairline-thin, which is what makes it read as
     the original flat design rather than a softened copy. */
  --sh-1: none;
  --sh-2: none;
  --sh-3: 0 4px 16px rgba(14,39,67,.14);
  --btn-bg: var(--teal);
  --accent-soft: var(--hover);
  --accent-ink: var(--teal-lo);
  --bar-bg: var(--navy);
  --bar-ink: #ffffff;
  --bar-muted: #b9c8dd;
  --bar-line: rgba(255,255,255,.25);
  --card-lift: 0;
  --mark: url('img/mark-light.png');
}

/* ---------- ATELIER: warm, bronze from the logo, quiet luxury ---------- */
body[data-theme="atelier"] {
  --sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --display: 'Plus Jakarta Sans', system-ui, sans-serif;
  --display-weight: 800; --display-tracking: -0.03em;

  --navy: #1b1611; --text: #1b1611; --muted: #857a6d;
  --page: #faf8f5; --surface: #ffffff; --hover: #f4f0e9;
  --rule: #ece5da; --rule-hi: #dcd2c3;
  --teal: #8a5a22; --teal-lo: #6d4517; --maroon: #7d1f2e;

  --accent-soft: #f6efe4; --accent-ink: #6d4517;
  --btn-bg: linear-gradient(180deg, #a06a2a 0%, #8a5a22 100%);
  --bar-bg: linear-gradient(180deg, #ffffff 0%, #fdfbf8 100%);
  --bar-ink: #1b1611; --bar-muted: #857a6d; --bar-line: rgba(27,22,17,.12);
  --mark: url('img/mark.png');

  --r: 12px; --r-lg: 16px;
  --sh-1: 0 1px 2px rgba(60,40,15,.05);
  --sh-2: 0 2px 4px rgba(60,40,15,.04), 0 6px 16px rgba(60,40,15,.07);
  --sh-3: 0 4px 8px rgba(60,40,15,.05), 0 16px 40px rgba(60,40,15,.11);
  --card-lift: -3px;
}

/* ---------- MERIDIAN: deep navy, glass, gradient accents ---------- */
body[data-theme="meridian"] {
  --sans: 'Manrope', system-ui, sans-serif;
  --display: 'Manrope', system-ui, sans-serif;
  --display-weight: 800; --display-tracking: -0.032em;

  --navy: #0d1b2a; --text: #0d1b2a; --muted: #6b7c90;
  --page: #f5f7fa; --surface: #ffffff; --hover: #eef2f7;
  --rule: #e3e9f0; --rule-hi: #cdd7e3;
  --teal: #0f766e; --teal-lo: #0b5b55; --maroon: #9f1239;

  --accent-soft: #e6f4f2; --accent-ink: #0b5b55;
  --btn-bg: linear-gradient(180deg, #12897f 0%, #0f766e 100%);
  --bar-bg: linear-gradient(120deg, #0b1e33 0%, #143252 55%, #10446b 100%);
  --bar-ink: #ffffff; --bar-muted: rgba(255,255,255,.66); --bar-line: rgba(255,255,255,.16);

  --r: 12px; --r-lg: 16px;
  --sh-1: 0 1px 2px rgba(13,27,42,.06);
  --sh-2: 0 2px 4px rgba(13,27,42,.05), 0 8px 20px rgba(13,27,42,.08);
  --sh-3: 0 6px 12px rgba(13,27,42,.07), 0 20px 48px rgba(13,27,42,.14);
  --card-lift: -3px;
}

/* ---------- PRESS: editorial. Serif at DISPLAY SIZE ONLY, because a
     serif everywhere was tried and read as too much. ---------- */
body[data-theme="press"] {
  --sans: 'Inter', system-ui, sans-serif;
  --display: 'Instrument Serif', Georgia, serif;
  --display-weight: 400; --display-tracking: -0.005em;

  --navy: #17140f; --text: #17140f; --muted: #7d7367;
  --page: #fbf9f6; --surface: #ffffff; --hover: #f4efe8;
  --rule: #eae3d9; --rule-hi: #d8cec1;
  --teal: #7d1f2e; --teal-lo: #641824; --maroon: #7d1f2e;

  --accent-soft: #f8eeef; --accent-ink: #641824;
  --btn-bg: linear-gradient(180deg, #8f2534 0%, #7d1f2e 100%);
  --bar-bg: linear-gradient(180deg, #ffffff 0%, #fbf8f4 100%);
  --bar-ink: #17140f; --bar-muted: #7d7367; --bar-line: rgba(23,20,15,.14);
  --mark: url('img/mark.png');

  --r: 8px; --r-lg: 10px;
  --sh-1: 0 1px 2px rgba(40,25,15,.05);
  --sh-2: 0 2px 6px rgba(40,25,15,.06), 0 8px 20px rgba(40,25,15,.06);
  --sh-3: 0 6px 14px rgba(40,25,15,.08), 0 20px 44px rgba(40,25,15,.10);
  --card-lift: -2px;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--page);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
[hidden] { display: none !important; }

/* ---------------------------------------------------------------------------
   SHELL
   --------------------------------------------------------------------------- */
.topbar {
  background: var(--bar-bg); color: var(--bar-ink);
  border-bottom: 1px solid var(--bar-line);
  padding: 10px 16px;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
}
/* The mark, not the full lockup: at this size the school-name line in the
   lockup is unreadable and just adds noise. Cream rather than the bronze
   original, which is dark-on-dark against the navy bar. */
.brandwrap { display: flex; align-items: center; gap: 9px; margin-right: 4px; }
/* A background rather than an <img>, so the mark follows the theme: cream on
   the dark bars, bronze on the light ones. Two files, one variable, no chance
   of the markup and the palette disagreeing. */
.brandwrap .mark {
  width: 30px; height: 26px; display: block; flex: none;
  background: var(--mark) center/contain no-repeat; opacity: .95;
}

.brand { display: flex; flex-direction: column; line-height: 1.2; }
.brand b { font-family: var(--display); font-size: 15.5px; font-weight: 600; letter-spacing: .01em; }
.brand span { font-size: 10.5px; color: var(--bar-muted); letter-spacing: .08em; text-transform: uppercase; }

.nav { display: flex; gap: 2px; flex-wrap: wrap; }
.nav a {
  color: var(--bar-muted); text-decoration: none; font-size: 13.5px;
  padding: 7px 12px; border-radius: 6px; min-height: 34px;
  display: inline-flex; align-items: center;
}
.nav a:hover { color: var(--bar-ink); background: rgba(127,127,127,.10); }
.nav a.on { color: var(--bar-ink); background: rgba(127,127,127,.16); font-weight: 600; }

.who { margin-left: auto; display: flex; align-items: center; gap: 10px; font-size: 13px; }
.who .nm { color: var(--bar-muted); }
.who button {
  font: inherit; font-size: 12.5px; cursor: pointer;
  background: transparent; color: var(--bar-muted); border: 1px solid var(--bar-line);
  border-radius: 6px; padding: 4px 10px;
}
.who button:hover { color: var(--bar-ink); border-color: var(--bar-muted); }

/* The teacher's own photo, from their student record. Sized to the row rather
   than to the image, and centred on the upper portion because these are
   portrait crops where the face sits high. The initials underneath show
   through until a photo actually loads, so there is never a blank disc. */
.who .av {
  width: 26px; height: 26px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 10.5px; font-weight: 700; letter-spacing: .02em;
  color: var(--bar-muted);
  background: var(--bar-line) center 22% / cover no-repeat;
  border: 1px solid var(--bar-line);
}
.who .av.has-photo { color: transparent; border-color: rgba(255,255,255,.28); }

/* Narrow: the three groups wrapping one-per-line cost most of a phone screen
   before a single student appeared. Brand and sign-out share the first row;
   the nav becomes a scrolling strip under it. */
@media (max-width: 640px) {
  .who .nm { display: none; }
  .topbar { padding: 8px 14px 0; gap: 0 10px; }
  .who { order: 2; }
  .nav {
    order: 3; width: 100%; flex-wrap: nowrap; overflow-x: auto;
    padding: 4px 0 6px; scrollbar-width: none;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav a { white-space: nowrap; }
  .periodbar { padding: 7px 14px; }
  .pivot { margin-left: 0; width: 100%; }
  .pivot select { flex: 1; max-width: none; }
}

.periodbar {
  background: var(--surface); border-bottom: 1px solid var(--rule);
  padding: 7px 16px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--muted);
}
.chip {
  display: inline-block; padding: 1px 9px; border-radius: 20px;
  background: var(--accent-soft); color: var(--accent-ink);
  font-weight: 600; font-size: 12px; letter-spacing: .02em;
}
/* Deficiency form. Three categories side by side on a desktop and stacked on
   a phone, each a single column of checkboxes -- the owner asked for exactly
   this shape on the print form, and the reason holds here too: a grid of
   toggles reads as a wall, a column reads as a list. */
.defcats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
.defcats.one { grid-template-columns: 1fr; }
@media (max-width: 720px) { .defcats { grid-template-columns: 1fr; } }
.defcat {
  border: 1px solid var(--rule); border-radius: 8px;
  padding: 10px 12px 12px; margin: 0; min-width: 0;
}
.defcat legend {
  padding: 0 6px; font-size: 11.5px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
}
.defopt {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 3px 0; font-size: 13.5px; line-height: 1.35; cursor: pointer;
}
.defopt input { margin: 2px 0 0; flex: none; }
.defother { margin-top: 8px; }
.defcats.one .defcat { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
.defcats.one .defcat legend { grid-column: 1 / -1; }
.defcats.one .defother { grid-column: 1 / -1; }
@media (max-width: 720px) { .defcats.one .defcat { grid-template-columns: 1fr; } }

/* On a phone the profile is long enough that the actions scroll off before
   anybody has finished reading, so they stick to the bottom instead. Full
   bleed, because a sticky bar inset from the edges reads as a floating card
   that happens to be stuck rather than as a toolbar. */
@media (max-width: 720px) {
  #v-student .toolbar {
    position: sticky; bottom: 0; z-index: 5;
    margin: 16px -14px 0; padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
    background: var(--surface); border-top: 1px solid var(--rule);
    box-shadow: 0 -6px 14px rgba(0,0,0,.06);
    display: flex; gap: 8px;
  }
  #v-student .toolbar .btn { flex: 1; }
  /* Unstuck while composing: the form appears BELOW the toolbar, so leaving
     it pinned would park the buttons in the middle of what you are writing. */
  #v-student:has(#composebox:not([hidden])) .toolbar { position: static; box-shadow: none; }
}

/* Compose sits under the profile toolbar rather than in a modal: the teacher
   is writing about the boy whose details are on screen, and covering them up
   to do it is the wrong trade. */
.composebox { margin-top: 14px; }
.composebox .formcard { margin: 0; }

/* The review panel spans the whole row, under the buttons. */
.defrow { flex-wrap: wrap; }
.defexpand {
  flex-basis: 100%; margin-top: 10px; padding-top: 12px;
  border-top: 1px dashed var(--rule);
  display: grid; gap: 10px;
}

/* Deliberately the loudest thing in the bar: it exists to stop somebody
   forgetting they are in preview and reporting the theme as broken. */
.themeprev {
  margin-left: auto; display: inline-flex; align-items: center; gap: 8px;
  padding: 2px 6px 2px 10px; border-radius: 20px;
  background: var(--accent-soft); color: var(--accent-ink);
  font-size: 12px; font-weight: 600;
}
.themeprev b { text-transform: capitalize; }
.themeprev button {
  font: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer;
  padding: 2px 9px; border-radius: 20px;
  border: 1px solid currentColor; background: transparent; color: inherit;
}
.themeprev button:hover { background: rgba(0,0,0,.06); }
/* Both claim margin-left:auto; whichever comes second would lose its spacing. */
.themeprev + .pivot, .pivot + .themeprev { margin-left: 0; }

.pivot { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.pivot select {
  font: inherit; font-size: 13px; padding: 4px 8px; border-radius: 6px;
  border: 1px solid var(--rule-hi); background: var(--surface); color: var(--text);
  max-width: 230px;
}

/* Admin-only. The ten-minute snapshot cache is what keeps the portal fast and
   also what makes a setting just changed in candl look like it did nothing. */
#refreshbtn {
  font: inherit; font-size: 12px; cursor: pointer; margin-left: auto;
  background: var(--surface); color: var(--muted); border: 1px solid var(--rule-hi);
  border-radius: 6px; padding: 4px 10px; min-height: 30px;
}
#refreshbtn:hover { background: var(--hover); color: var(--text); }
#refreshbtn:disabled { opacity: .6; cursor: default; }
#refreshbtn + .pivot { margin-left: 8px; }

#toast {
  position: fixed; left: 50%; bottom: 22px; transform: translate(-50%, 10px);
  background: var(--navy); color: #fff; font-size: 13.5px;
  padding: 10px 18px; border-radius: 20px; box-shadow: 0 4px 16px rgba(14,39,67,.22);
  opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease;
  z-index: 60; max-width: calc(100vw - 32px); text-align: center;
}
#toast.on { opacity: 1; transform: translate(-50%, 0); }

/* customConfirm() -- a confirm dialog styled like this app, not the browser. */
.modal-overlay {
  position: fixed; inset: 0; background: rgba(14, 39, 67, .38);
  display: flex; align-items: center; justify-content: center;
  z-index: 100; padding: 20px;
}
.modal-card {
  background: var(--surface); border-radius: var(--r);
  box-shadow: var(--sh-2, 0 8px 24px rgba(0,0,0,.2));
  padding: 20px; max-width: 380px; width: 100%;
}
.modal-msg { font-size: 14.5px; color: var(--text); line-height: 1.5; margin: 0 0 18px; white-space: pre-line; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; }
/* The field customPrompt adds. Sits where the message ends and the buttons
   begin, so the card reads top to bottom as one thing. */
.modal-input {
  width: 100%; margin: 0 0 18px; padding: 9px 11px;
  border: 1px solid var(--line, #e3e3e8); border-radius: 8px;
  font: inherit; font-size: 14.5px; background: var(--bg, #fff); color: var(--text);
}
.modal-input:focus { outline: 2px solid var(--accent, #2a6ef0); outline-offset: -1px; }

main { max-width: 1080px; margin: 0 auto; padding: 20px 16px 60px; }

/* ---------------------------------------------------------------------------
   TYPE + CONTROLS
   --------------------------------------------------------------------------- */
h1 { font-family: var(--display); font-size: 21px; font-weight: var(--display-weight); margin: 0 0 2px; letter-spacing: var(--display-tracking); }
h2 { font-family: var(--display); font-size: 16px; font-weight: var(--display-weight); margin: 0; letter-spacing: var(--display-tracking); }
.sub { color: var(--muted); font-size: 13.5px; margin: 0 0 18px; }
.eyebrow {
  font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); font-weight: 600; margin: 0 0 8px;
}

.btn {
  font: inherit; font-size: 14px; cursor: pointer;
  min-height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 0 16px; border-radius: 6px;
  background: var(--btn-bg); color: #fff; border: 1px solid var(--teal-lo);
  box-shadow: var(--sh-1); transition: transform .1s ease, box-shadow .18s ease, filter .18s ease;
}
.btn:hover { filter: brightness(1.06); box-shadow: var(--sh-2); }
.btn:active { transform: translateY(1px); }
.btn.ghost { background: var(--surface); color: var(--navy); border-color: var(--rule-hi); }
.btn.ghost:hover { background: var(--hover); }
.btn:disabled { opacity: .5; cursor: default; }
.btn:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, button:focus-visible {
  outline: 2px solid var(--teal); outline-offset: 2px;
}

.toolbar { display: flex; gap: 10px; align-items: center; margin: 0 0 14px; flex-wrap: wrap; }
.search {
  flex: 1 1 200px; min-height: var(--tap);
  border: 1px solid var(--rule-hi); border-radius: 6px;
  padding: 0 12px; font: inherit; font-size: 14px; background: var(--surface); color: var(--text);
}
.search::placeholder { color: #8fa09d; }

.chips { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 14px; }
.fchip {
  font: inherit; font-size: 12.5px; cursor: pointer;
  padding: 6px 12px; border-radius: 20px; min-height: 32px;
  background: var(--surface); color: var(--muted); border: 1px solid var(--rule-hi);
}
.fchip:hover { background: var(--hover); }
.fchip.on { background: var(--btn-bg); border-color: transparent; color: #fff; font-weight: 600; box-shadow: var(--sh-1); }

/* TABS, for switching PAGE -- as distinct from .fchip, which filters what is
   already on the page. Admin and Requests were both built from .fchip, so a
   page could open with two near-identical pill rows stacked (subtabs, then a
   date range) where only one of them navigated, and nothing but position said
   which. An underlined tab row is the one shape everybody already reads as
   "these are the sections", and it leaves the pill free to mean "filter".

   --accent-ink, not --btn-bg: the button token is a gradient in three of the
   four themes, and a gradient cannot be a border-color -- it would silently
   resolve to nothing and the active tab would lose its underline. */
.tabs {
  display: flex; gap: 2px; flex-wrap: wrap;
  border-bottom: 1px solid var(--rule);
  margin: 0 0 16px;
}
.tab {
  font: inherit; font-size: 13.5px; cursor: pointer;
  padding: 8px 14px; min-height: 36px;
  background: none; color: var(--muted);
  border: 0; border-bottom: 2px solid transparent;
  /* Sits ON the container rule rather than above it, so the active underline
     replaces that hairline instead of doubling it. */
  margin-bottom: -1px;
}
.tab:hover:not(.on) { color: var(--text); background: var(--hover); }
.tab.on { color: var(--accent-ink); border-bottom-color: var(--accent-ink); font-weight: 650; }

/* ===========================================================================
   REVIEW — the deficiency approval workspace
   ---------------------------------------------------------------------------
   Queue on the left, one report on the right. The rail is what makes moving
   between reports free; the pane is a normal form, which is the point --
   an approver edits with the same checkboxes the teacher submitted with.
   =========================================================================== */
.navcount {
  display: inline-block; margin-left: 6px; padding: 0 6px;
  min-width: 17px; border-radius: 9px;
  background: var(--accent-ink); color: #fff;
  font-size: 11px; font-weight: 700; line-height: 17px; text-align: center;
}

.rv { display: grid; grid-template-columns: 260px 1fr; gap: 22px; align-items: start; }
/* One column below the breakpoint, rail first: on a phone the queue IS the
   screen, and the report opens under it rather than beside it. */
@media (max-width: 860px) { .rv { grid-template-columns: 1fr; } }

.rvrail {
  border: 1px solid var(--rule); border-radius: var(--r);
  background: var(--surface); overflow: hidden;
  position: sticky; top: 12px;
}
@media (max-width: 860px) { .rvrail { position: static; } }

.rvhead {
  display: block; width: 100%; text-align: left;
  padding: 9px 12px; background: var(--hover);
  border: 0; border-bottom: 1px solid var(--rule);
  font-size: 11px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  color: var(--muted); font-family: inherit;
}
.rvtoggle { cursor: pointer; border-top: 1px solid var(--rule); }
.rvtoggle:hover { color: var(--text); }
.rvcount {
  display: inline-block; margin-left: 4px; padding: 0 5px;
  border-radius: 8px; background: var(--accent-ink); color: #fff;
  font-size: 10.5px; line-height: 15px;
}
.rvempty { padding: 12px; margin: 0; }

.rvrow {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 8px;
  width: 100%; text-align: left; cursor: pointer;
  padding: 9px 12px; border: 0; border-bottom: 1px solid var(--rule);
  background: none; font: inherit; color: var(--text);
  /* The accent bar marks the open row. Reserved on every row rather than
     added to the open one, so selecting does not shift the text sideways. */
  box-shadow: inset 3px 0 0 transparent;
}
.rvrow:hover { background: var(--hover); }
.rvrow.on { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent-ink); }
.rvrow.dim .rvname { color: var(--muted); }
.rvname { font-size: 13.5px; font-weight: 600; }
.rvage { font-size: 11.5px; color: var(--muted); white-space: nowrap; }
.rvsub {
  grid-column: 1 / -1; font-size: 11.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.rvpane {
  border: 1px solid var(--rule); border-radius: var(--r);
  background: var(--surface); padding: 18px 20px;
}
/* The rail has no padding of its own -- its rows draw their own -- so the
   loading skeleton needs it here or the bars sit flush against the border. */
.rvrail > .skel { padding: 12px; }
/* Shorter than the 56px default: these stand in for two-line rows, and
   full-height bars read as a much longer queue than the one that arrives. */
.rvrail > .skel .skelrow { height: 42px; }
.rvtitle { font-size: 17px; font-weight: 700; }
.rvdim { color: var(--muted); font-weight: 400; }
.rvmeta { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.rvlist { margin: 4px 0 0; padding-left: 20px; font-size: 14px; line-height: 1.55; }
.rvquote {
  margin: 4px 0 0; padding-left: 12px; border-left: 2px solid var(--rule-hi);
  font-size: 14px; line-height: 1.6; white-space: pre-wrap;
}
.rvactions { display: flex; gap: 10px; margin-top: 18px; }

/* A quiet control, for something correct but rarely wanted. Re-sending a
   letter is not the job on this screen -- deciding is -- so it reads as a small
   piece of text rather than competing with the buttons that do the work. It
   still confirms through the full preview, so restraint here costs no safety. */
.rvquietrow { margin-top: 20px; padding-top: 12px; border-top: 1px solid var(--rule); }
.rvquiet {
  font: inherit; font-size: 12px; cursor: pointer;
  padding: 0; border: 0; background: none;
  color: var(--muted); text-decoration: underline;
}
.rvquiet:hover:not(:disabled) { color: var(--text); }
.rvquiet:disabled { cursor: default; text-decoration: none; opacity: .6; }

.rvarchive {
  padding: 12px; border-top: 1px solid var(--rule);
  background: var(--hover); font-size: 12px; line-height: 1.7;
}
.rvarchtitle {
  font-size: 11px; font-weight: 700; letter-spacing: 1px;
  text-transform: uppercase; color: var(--muted); margin-bottom: 4px;
}
.rvarchive a { color: var(--accent-ink); }
.rvarchnote { margin-top: 6px; color: var(--muted); font-size: 11.5px; line-height: 1.5; }

/* Air above each section heading, so the three question groups read as three
   groups rather than one long column.
   Scoped to this pane on purpose. `.gap` is carried by the markup on several
   screens and has never had a rule anywhere -- it does nothing today, so
   defining it globally would silently reflow the submit form and the prefs
   page as a side effect of a change asked for here. */
.rvpane .eyebrow.gap { margin-top: 26px; }
/* Except the first one, which already sits under the report header and would
   otherwise be the only heading pushed away from the thing it belongs to. */
.rvpane .rvmeta + .eyebrow.gap { margin-top: 18px; }

/* The letter. A dialog rather than a panel because it is the last thing
   between a draft and a family's inbox, and it should take the whole screen
   rather than sit in a corner of the one being edited. */
.rvmodal {
  position: fixed; inset: 0; z-index: 90;
  background: rgba(16, 29, 28, 0.45);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 40px 16px; overflow: auto;
}
.rvsheet {
  width: 640px; max-width: 100%;
  background: var(--surface); border-radius: 10px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.28);
  padding: 20px;
}
.rvsheethead {
  display: flex; gap: 12px; align-items: flex-start; justify-content: space-between;
  padding-bottom: 12px; border-bottom: 1px solid var(--rule);
}
.rvletter {
  margin-top: 16px; padding: 20px;
  border: 1px solid var(--rule); border-radius: 6px;
  background: var(--page);
  font-size: 14.5px; line-height: 1.65;
}
.rvletter ul { margin: 6px 0 12px; padding-left: 22px; }
.rvnote { margin-top: 10px; }

/* A SEGMENTED CONTROL, deliberately not more .fchip -- Contact faculty's
   Email/Text/Both sits directly under the page's own subtab row, which is
   built from .fchip, and the two read as one confusing row of pills when
   they share a shape and an accent color. One joined pill (no gaps, no
   individual borders) in the neutral --navy reads as "a setting for this
   form" rather than "more tabs to click". */
.seg { display: inline-flex; border: 1px solid var(--rule-hi); border-radius: 8px; overflow: hidden; }
.seg button {
  font: inherit; font-size: 13px; cursor: pointer; padding: 7px 16px;
  border: none; border-right: 1px solid var(--rule-hi);
  background: var(--surface); color: var(--text);
}
.seg button:last-child { border-right: none; }
.seg button:hover:not(.on) { background: var(--hover); }
.seg button.on { background: var(--navy); color: #fff; font-weight: 600; }

/* A select that reads as a button: same height, weight and border as .btn
   ghost, so a toolbar of buttons does not visibly contain one dropdown. Used
   where the control IS the menu -- picking an option acts immediately rather
   than setting something. */
select.btnsel {
  font: inherit; font-size: 13.5px; font-weight: 600;
  min-height: var(--tap); padding: 0 8px;
  border: 1px solid var(--rule-hi); border-radius: 8px;
  background: var(--surface); color: var(--text); cursor: pointer;
}
select.btnsel:hover { background: var(--hover); }

select.facet {
  font: inherit; font-size: 13px; min-height: 36px; padding: 0 8px;
  border-radius: 6px; border: 1px solid var(--rule-hi);
  background: var(--surface); color: var(--text); max-width: 200px;
}

.backlink {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--teal); text-decoration: none; font-size: 13.5px;
  min-height: 32px; margin: 0 0 8px; background: none; border: 0; cursor: pointer; font-family: inherit;
}
.backlink:hover { text-decoration: underline; }

/* A .backlink used as the TITLE of a list row, rather than standing on its
   own. The base rule carries min-height 32px and an 8px bottom margin, which
   are right for a "back" affordance with nothing around it and add roughly
   forty wasted pixels to every row when one is nested inside one. Inherit the
   row's own type as well, or the title renders smaller than the plain-text
   titles in the rows above it. */
/* The count line, with the actions that operate on it sitting at the far end.
   Wraps rather than compressing: on a narrow screen the buttons drop under the
   count instead of squeezing a select until it clips its own longest option,
   which is what happened when all six controls shared the filter bar. */
.statrow {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px 12px; flex-wrap: wrap; margin: 0 0 10px;
}
.statact { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.statact .facet { max-width: 150px; min-height: 32px; font-size: 12.5px; }

/* Repository tiles. auto-fill with a minimum rather than a fixed count, so
   the grid reflows from four across on a desktop to one on a phone without a
   breakpoint per size. */
.tiles {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
}
.tile {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 14px 14px 12px; min-height: 128px;
  background: var(--surface); border: 1px solid var(--rule); border-radius: 10px;
  font: inherit; text-align: left; cursor: pointer; color: var(--text);
  transition: border-color .15s, box-shadow .15s, transform .15s;
}
.tile:hover {
  border-color: var(--rule-hi); box-shadow: var(--sh-1); transform: translateY(-1px);
}
.tileicon { color: var(--teal); line-height: 0; margin-bottom: 6px; }
.tileicon.lg svg { width: 48px; height: 48px; }
/* The subject is the thing being looked for, so it carries the weight; the
   teacher is how you tell two Geometry repositories apart. */
.tiletitle {
  font-size: 15px; font-weight: 600; color: var(--navy); line-height: 1.25;
  overflow-wrap: anywhere;
}
.tilesub { font-size: 12.5px; color: var(--muted); overflow-wrap: anywhere; }
.tilecount { margin-top: auto; padding-top: 6px; font-size: 12px; color: var(--teal); }

.abwhen .backlink {
  min-height: 0; margin: 0; padding: 0;
  font-size: inherit; font-weight: inherit; line-height: inherit;
  text-align: left;
}

.notice {
  border: 1px solid var(--rule-hi); border-left: 3px solid var(--teal);
  background: var(--surface); border-radius: 6px;
  padding: 10px 14px; font-size: 13px; color: var(--muted); margin: 0 0 16px;
}
.footnote {
  margin-top: 14px; font-size: 12.5px; color: var(--muted);
  border-left: 2px solid var(--rule-hi); padding-left: 10px;
}

/* ---------------------------------------------------------------------------
   CLASS CARDS
   --------------------------------------------------------------------------- */
.classgrid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }
.classcard {
  display: flex; align-items: center; gap: 14px; text-align: left;
  width: 100%; font: inherit; cursor: pointer;
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--r-lg); padding: 14px 16px; min-height: 74px;
  box-shadow: var(--sh-1);
  transition: transform .18s cubic-bezier(.2,.8,.2,1), box-shadow .18s ease, border-color .18s ease;
}
.classcard:hover {
  background: var(--surface); border-color: var(--rule-hi);
  transform: translateY(var(--card-lift)); box-shadow: var(--sh-2);
}
.classcard .cc-main { min-width: 0; flex: 1; }
.classcard .cc-name { display:block; font-family: var(--display); font-weight: var(--display-weight); letter-spacing: var(--display-tracking); font-size: 15px; color: var(--navy); }
.classcard .cc-meta { display:block; font-size: 12.5px; color: var(--muted); margin-top: 1px; }
.classcard .cc-count {
  font-variant-numeric: tabular-nums; font-weight: 600; color: var(--navy); font-size: 15px; white-space: nowrap;
}
.classcard .cc-count small { display: block; font-weight: 400; font-size: 11px; color: var(--muted); text-align: right; }
.classcard.all { grid-column: 1 / -1; border-color: var(--rule-hi); background: var(--hover); }

/* ---------------------------------------------------------------------------
   AVATARS
   --------------------------------------------------------------------------- */
.av {
  width: 38px; height: 38px; border-radius: 50%; flex: none;
  display: grid; place-items: center; overflow: hidden;
  font-size: 12.5px; font-weight: 600; letter-spacing: .02em;
  background: var(--hover); color: var(--navy);
  border: 1px solid var(--rule);
}
.av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.av.lg { width: 96px; height: 96px; font-size: 28px; }

/* ---------------------------------------------------------------------------
   ROSTER — one list, regrading by width
   --------------------------------------------------------------------------- */
.rosterhead {
  display: grid; gap: 12px; align-items: center;
  grid-template-columns: 38px 1.6fr .6fr 1fr .9fr .45fr 1.2fr;
  padding: 0 12px 8px; border-bottom: 1px solid var(--rule-hi);
}
.rosterhead span {
  font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); font-weight: 600;
}
.row {
  display: grid; gap: 12px; align-items: center;
  grid-template-columns: 38px 1.6fr .6fr 1fr .9fr .45fr 1.2fr;
  width: 100%; text-align: left; font: inherit; cursor: pointer;
  background: none; border: 0; border-bottom: 1px solid var(--rule);
  padding: 8px 12px; min-height: 56px; color: inherit;
}
.row:hover { background: var(--hover); }
.row .nm { font-weight: 600; color: var(--navy); }
.row .cell { font-size: 13.5px; color: var(--muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .sub2 { display: none; }
.row .contact { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .contact a { color: var(--teal); text-decoration: none; }
.row .contact a:hover { text-decoration: underline; }
.chev { color: var(--rule-hi); font-size: 18px; display: none; }

@media (max-width: 720px) {
  .rosterhead { display: none; }
  .row { grid-template-columns: 38px 1fr auto; min-height: 60px; }
  .row .cell, .row .contact { display: none; }
  .row .sub2 { display: block; font-size: 12.5px; color: var(--muted); margin-top: 1px; }
  .chev { display: block; }
}

.badge {
  display: inline-block; font-size: 10px; font-weight: 600; letter-spacing: .05em;
  padding: 1px 7px; border-radius: 4px; text-transform: uppercase; vertical-align: 1px;
  background: var(--hover); color: var(--muted); border: 1px solid var(--rule);
}
.badge.both { background: #f7ecee; color: var(--maroon); border-color: #e4c8cd; }

/* ---------------------------------------------------------------------------
   STUDENT CARD
   --------------------------------------------------------------------------- */
.card2col { display: grid; gap: 22px; grid-template-columns: 220px 1fr; align-items: start; }
@media (max-width: 720px) { .card2col { grid-template-columns: 1fr; gap: 16px; } }

.idblock { display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; }
.idblock .nm { font-family: var(--display); font-size: 18px; font-weight: var(--display-weight); letter-spacing: var(--display-tracking); color: var(--navy); }

.factgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: var(--r); overflow: hidden; margin: 0; }
.fact { background: var(--surface); padding: 10px 12px; }
.fact dt { font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); font-weight: 600; }
.fact dd { margin: 2px 0 0; font-size: 14.5px; color: var(--navy); font-weight: 500; }

.contactlist { border: 1px solid var(--rule); border-radius: var(--r); overflow: hidden; margin-top: 6px; background: var(--surface); }
.contactrow { padding: 12px 14px; border-bottom: 1px solid var(--rule); }
.contactrow:last-child { border-bottom: 0; }
.contactrow .role { font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); font-weight: 600; }
.contactrow .pname { font-weight: 600; color: var(--navy); margin: 1px 0 6px; }
.contactacts { display: flex; flex-wrap: wrap; gap: 8px; }
.contactacts a {
  display: inline-flex; align-items: center; min-height: 38px;
  padding: 0 12px; border-radius: 6px;
  border: 1px solid var(--rule-hi); background: var(--surface);
  color: var(--teal); text-decoration: none; font-size: 13.5px;
}
.contactacts a:hover { background: var(--hover); }
.sectiongap { margin-top: 20px; }

/* ---------------------------------------------------------------------------
   STATES
   --------------------------------------------------------------------------- */
.empty {
  border: 1px dashed var(--rule-hi); border-radius: var(--r);
  padding: 26px 20px; text-align: center; background: var(--hover);
}
.empty h2 { margin-bottom: 6px; }
.empty p { color: var(--muted); font-size: 13.5px; margin: 0 auto; max-width: 46ch; }

.skel { display: grid; gap: 10px; }
.skelrow { height: 56px; border-radius: 6px; background: linear-gradient(90deg, var(--hover) 25%, #e2e9e7 37%, var(--hover) 63%); background-size: 400% 100%; animation: sh 1.3s ease-in-out infinite; }
@keyframes sh { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

.signin {
  min-height: 70vh; display: grid; place-items: center; text-align: center;
}
.signin .box { display: grid; gap: 14px; justify-items: center; max-width: 40ch; }
/* Restrained on purpose. The full lockup at hero size read as clunky; at this
   width it identifies the school and then gets out of the way. */
.signin .logo { width: min(190px, 62vw); height: auto; margin-bottom: 2px; }
.err { color: var(--bad); font-size: 13.5px; }

/* ---------------------------------------------------------------------------
   FORMS  (absence notice, and whatever write features follow it)
   --------------------------------------------------------------------------- */
.formcard {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r);
  padding: 18px; display: grid; gap: 14px;
}
.frow { display: grid; gap: 14px; grid-template-columns: 1fr 1fr; }
.frow.end { grid-template-columns: 1fr auto; align-items: center; }
/* Two or more buttons in the trailing cell, so the grid still sees one child
   and none of them stretches to a full column. */
.btnrow { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }

/* Formatting bar. Sits directly on the textarea it belongs to, squared off at
   the bottom so the two read as one control rather than a toolbar floating
   above a box. */
.fmtbar { display: flex; gap: 2px; margin-bottom: -1px; }
.fmtbar button {
  font: inherit; font-size: 13px; line-height: 1; cursor: pointer;
  min-width: 30px; height: 28px; padding: 0 7px;
  background: var(--surface); color: var(--muted);
  border: 1px solid var(--rule-hi); border-radius: 6px 6px 0 0;
}
.fmtbar button:hover { background: var(--hover); color: var(--text); }
.fld .fmtbar + textarea { border-top-left-radius: 0; }

/* The editor. Matches the inputs beside it so the form still reads as one
   set of controls rather than a document pane bolted into a form. */
.rte {
  font: inherit; font-size: 14px; color: var(--text); background: var(--surface);
  border: 1px solid var(--rule-hi); border-radius: 0 6px 6px 6px;
  padding: 10px 11px; min-height: 150px; width: 100%;
  line-height: 1.5; overflow-y: auto;
}
.rte:focus { outline: 2px solid var(--teal); outline-offset: 1px; border-color: var(--teal); }
/* Placeholder, since contenteditable has none of its own. :empty alone is not
   enough - a browser leaves a stray <br> behind after the last character is
   deleted, so the box would look filled while being empty. */
.rte:empty::before,
.rte:has(> br:only-child)::before {
  content: attr(data-placeholder); color: var(--muted); pointer-events: none;
}
.rte p { margin: 0 0 12px; }
.rte ul { margin: 0 0 12px; padding-left: 22px; }
.rte a { color: var(--teal); }
@media (max-width: 560px) {
  .frow { grid-template-columns: 1fr; }
  .frow.end { grid-template-columns: 1fr; }
  .frow.end .btn { width: 100%; }
}
/* align-content: start is load-bearing. Side by side in a .frow, two .fld
   grids stretch to the same height — and a column WITHOUT a hint under its
   input grows its own rows to fill, making that input 11px taller and 11px
   lower than its neighbour. Pinning the rows to their natural size is what
   keeps the two inputs on one line. */
.fld { display: grid; gap: 5px; align-content: start; }
.fld > span { font-size: 12.5px; font-weight: 600; color: var(--text); }
.fld small { font-size: 11.5px; color: var(--muted); }
/* select belongs in this rule and was missing from it, so every dropdown fell
   back to the raw OS control and sat visibly apart from the inputs beside it.
   It only became obvious on the deficiency form, which is the first to lean
   on selects. */
.fld input, .fld textarea, .fld select {
  font: inherit; font-size: 14px; color: var(--text); background: var(--surface);
  border: 1px solid var(--rule-hi); border-radius: 6px; padding: 9px 11px;
  min-height: var(--tap); width: 100%;
}
.fld textarea { min-height: 74px; resize: vertical; line-height: 1.5; }
/* Native arrow dropped for one drawn in the text colour, so it follows the
   theme. A background-image chevron cannot: a data URI takes a literal
   colour, which then reads wrong in whichever theme it was not picked for. */
.fld select {
  appearance: none; -webkit-appearance: none;
  padding-right: 32px; cursor: pointer;
}
.fld { position: relative; }
.fld:has(select)::after {
  content: ''; position: absolute; right: 13px; bottom: 18px;
  width: 7px; height: 7px; pointer-events: none;
  border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted);
  transform: translateY(-50%) rotate(45deg);
}
.fld select:disabled { color: var(--muted); cursor: default; }
.fld input:focus, .fld textarea:focus, .fld select:focus {
  outline: 2px solid var(--teal); outline-offset: 1px; border-color: var(--teal);
}

.badge.ok { background: #e6f2f0; color: var(--teal-lo); border-color: #bcdcd8; }
.badge.warn { background: #fdf3e6; color: #8a5210; border-color: #f0dcc0; }

.ablist { border: 1px solid var(--rule); border-radius: var(--r); overflow: hidden; background: var(--surface); }
.abrow {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; border-bottom: 1px solid var(--rule);
}
.abrow:last-child { border-bottom: 0; }
.abrow.off { opacity: .58; }
.abmain { flex: 1; min-width: 0; }
.abwhen { font-weight: 600; color: var(--navy); font-size: 14px; }
.abmeta {
  font-size: 12.5px; color: var(--muted); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.abwarn { font-size: 12px; color: #8a5210; margin-top: 3px; }
/* The row currently loaded into the form, so it is obvious which notice the
   Save is about to change. */
.abrow.editing { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--teal); }

/* One date until a second is asked for. */
.daterange { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.daterange input[type="date"] {
  font: inherit; font-size: 14px; color: var(--text); background: var(--surface);
  border: 1px solid var(--rule-hi); border-radius: 6px; padding: 9px 11px;
  min-height: var(--tap);
}
.daterange input[type="date"]:focus { outline: 2px solid var(--teal); outline-offset: 1px; border-color: var(--teal); }
.rangearrow { color: var(--muted); font-size: 15px; }

/* ---------------------------------------------------------------------------
   DROPZONE
   The whole panel is the target, not a button inside it — a small hit area is
   the main reason drag and drop gets abandoned for the file dialog.
   --------------------------------------------------------------------------- */
.dropzone {
  position: relative; cursor: pointer;
  border: 2px dashed var(--rule-hi); border-radius: 10px;
  background: linear-gradient(180deg, #fbfcfe 0%, var(--surface) 100%);
  padding: 26px 18px; text-align: center;
  transition: border-color .15s ease, background .15s ease, transform .1s ease;
}
.dropzone:hover { border-color: var(--teal); background: #f7fbfa; }
.dropzone:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.dropzone.over {
  border-color: var(--teal); background: #eef7f5;
  border-style: solid; transform: scale(1.01);
}
.dropzone.has { border-style: solid; border-color: var(--rule-hi); padding: 14px; text-align: left; }

.dz-idle { display: grid; justify-items: center; gap: 3px; pointer-events: none; }
.dz-icon { width: 52px; height: 42px; color: var(--rule-hi); transition: color .15s ease; }
.dropzone:hover .dz-icon, .dropzone.over .dz-icon { color: var(--teal); }
.dz-title { font-size: 15px; color: var(--navy); font-weight: 600; }
.dropzone.over .dz-title::after { content: ' — let go'; color: var(--teal); font-weight: 500; }
.dz-sub { font-size: 12.5px; color: var(--muted); }
.dz-sub u { text-decoration-color: var(--rule-hi); }

.dz-file { display: flex; align-items: center; gap: 12px; }
.dz-thumb {
  width: 46px; height: 46px; flex: none; border-radius: 6px; overflow: hidden;
  display: grid; place-items: center;
}
.dz-thumb.img { border: 1px solid var(--rule); }
.dz-thumb.img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dz-thumb.ext {
  background: var(--navy); color: #fff;
  font-size: 11px; font-weight: 700; letter-spacing: .04em;
}
.dz-meta { flex: 1; min-width: 0; display: grid; }
.dz-meta b { color: var(--navy); font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dz-meta span { font-size: 12px; color: var(--muted); }

.dz-bar {
  position: absolute; left: 10px; right: 10px; bottom: 8px;
  height: 5px; border-radius: 3px; background: var(--hover); overflow: hidden;
}
.dz-bar i { display: block; height: 100%; width: 0; background: var(--teal); transition: width .15s ease; }

/* The two alternatives to dragging, on one line under the zone. */
.altrow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
.altrow input[type="url"] {
  flex: 1 1 200px; font: inherit; font-size: 13.5px; color: var(--text);
  background: var(--surface); border: 1px solid var(--rule-hi); border-radius: 6px;
  padding: 8px 11px; min-height: 38px;
}
.altsep { font-size: 12.5px; color: var(--muted); }
#pr-reuse-list { margin-top: 10px; }
#pr-reuse-list .abrow { cursor: pointer; text-align: left; width: 100%; font: inherit; }
#pr-reuse-list .abrow:hover { background: var(--hover); }

/* Finishing toggles: default on, so the common job needs no thought — and
   PLAIN, sharing a row with the notes field. As pills on their own row they
   were the loudest thing in the form and made it a screen taller, when they
   are the part nobody normally touches. */
.toggles { display: flex; flex-wrap: wrap; gap: 4px 16px; align-items: center; min-height: 44px; }
.toggle {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font-size: 13.5px; color: var(--text); white-space: nowrap;
}
.toggle input { width: auto; margin: 0; accent-color: var(--teal); }
.toggle:hover { color: var(--navy); }

.scopelabel {
  font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); font-weight: 700; align-self: center; margin-right: 4px;
}

.linkbtn {
  font: inherit; font-size: 12.5px; background: none; border: 0; padding: 0;
  color: var(--teal); cursor: pointer; text-align: left;
}
.linkbtn:hover { text-decoration: underline; }
.linkbtn:disabled { color: var(--muted); cursor: default; text-decoration: none; }
.btn.sm { min-height: 34px; font-size: 13px; padding: 0 12px; flex: none; }
.abrow .abmeta a { color: var(--teal); }

/* Mail: each item is a header button plus a collapsible sandboxed body. */
.mailitem { border-bottom: 1px solid var(--rule); }
.mailitem:last-child { border-bottom: 0; }
.mailhead { width: 100%; text-align: left; font: inherit; cursor: pointer; background: none; border: 0; color: inherit; }
.mailhead:hover { background: var(--hover); }
.mailbody { padding: 0 14px 14px; }
.mailframe {
  width: 100%; height: min(62vh, 640px); border: 1px solid var(--rule);
  border-radius: 6px; background: #fff;
}

/* The prefs gear in the topbar. */
#prefsbtn { font-size: 15px; line-height: 1; padding: 4px 9px; }

/* ---------------------------------------------------------------------------
   MONTH GRID
   --------------------------------------------------------------------------- */
.mhead { display: flex; align-items: center; justify-content: center; gap: 14px; margin: 0 0 12px; }
.mhead h2 { min-width: 11ch; text-align: center; }

.mgrid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px;
  background: var(--rule); border: 1px solid var(--rule);
  border-radius: var(--r); overflow: hidden;
}
.mdow {
  background: var(--bg-off, #f6f8fc); padding: 7px 6px; text-align: center;
  font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); font-weight: 600;
}
.mcell { background: var(--surface); min-height: 92px; padding: 5px 6px 7px; }
.mcell.out { background: var(--hover); }
.mcell.recess { background: #fdf6ec; }
.mcell.today { box-shadow: inset 0 0 0 2px var(--teal); }
.mnum { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; margin-bottom: 3px; }
.mcell.today .mnum { color: var(--teal); font-weight: 700; }
.mev {
  font-size: 11px; line-height: 1.25; border-radius: 4px; padding: 2px 5px;
  margin-bottom: 2px; background: var(--hover); color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  border-left: 2px solid var(--rule-hi);
}
.mev.recess { background: #fbeeda; border-left-color: #c98a2e; color: #7a4c10; }
.mev.milestone { background: #e3efee; border-left-color: var(--teal); color: var(--teal-lo); }
.mev.schedule { background: #eceff4; border-left-color: var(--navy); }
.mmore { font-size: 10.5px; color: var(--muted); padding-left: 5px; }

/* Narrow: a 7-column grid cannot shrink below legibility, so it scrolls
   sideways inside its own box rather than squeezing the page. */
@media (max-width: 700px) {
  .mgridwrap { overflow-x: auto; }
  .mgrid { min-width: 620px; }
  .mcell { min-height: 76px; }
}
.linkgrid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
.linkcard {
  display: flex; flex-direction: column; gap: 2px; text-decoration: none;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r);
  padding: 13px 15px; min-height: 64px;
}
.linkcard:hover { background: var(--hover); border-color: var(--rule-hi); }
.linkcard b { color: var(--navy); font-size: 14px; }
.linkcard span { color: var(--muted); font-size: 12.5px; }

/* ---------------------------------------------------------------------------
   GAME
   --------------------------------------------------------------------------- */
.game { max-width: 620px; margin: 0 auto; }

/* --- mode + level pickers --- */
.modegrid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.modecard {
  display: grid; justify-items: center; gap: 4px; text-align: center;
  font: inherit; cursor: pointer; padding: 18px 14px 16px;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r);
  transition: transform .12s ease, border-color .12s ease, box-shadow .12s ease;
}
.modecard:hover {
  transform: translateY(-2px); border-color: var(--teal);
  box-shadow: 0 6px 18px rgba(14,39,67,.08);
}
.modeart { width: 100px; height: 76px; display: block; }
.modename { font-family: var(--display); font-weight: 600; font-size: 15.5px; color: var(--navy); margin-top: 6px; }
.modeblurb { font-size: 12.5px; color: var(--muted); line-height: 1.4; }
.modebest {
  margin-top: 6px; font-size: 11px; font-weight: 700; letter-spacing: .04em;
  color: var(--teal-lo); background: #e7f2f0; border-radius: 20px; padding: 2px 10px;
}
.modehero { display: flex; align-items: center; gap: 14px; }
.modehero .modeart { width: 78px; height: 60px; flex: none; }

.levelgrid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.levelcard {
  display: grid; gap: 3px; text-align: left; font: inherit; cursor: pointer;
  padding: 14px 16px; background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--r);
  transition: transform .12s ease, border-color .12s ease;
}
.levelcard:hover { transform: translateY(-2px); border-color: var(--teal); }
.lvname { font-family: var(--display); font-weight: 600; font-size: 15px; color: var(--navy); }
.lvblurb { font-size: 12.5px; color: var(--muted); }
.lvmult { font-size: 11.5px; font-weight: 700; color: var(--teal-lo); margin-top: 2px; }

/* --- HUD --- */
.ghud {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: 10px; margin-bottom: 10px;
}
.gstat { display: flex; flex-direction: column; line-height: 1.05; }
.gstat.right { text-align: right; }
.gstat b { font-family: var(--display); font-size: 22px; color: var(--navy); font-variant-numeric: tabular-nums; }
.gstat b.bump { animation: bump .35s ease-out; }
@keyframes bump { 0% { transform: scale(1); } 40% { transform: scale(1.22); } 100% { transform: scale(1); } }
.gstat span { font-size: 10px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
.gstreak {
  font-family: var(--display); font-weight: 700; font-size: 17px; color: var(--maroon);
  background: #f7ecee; border-radius: 20px; padding: 3px 12px; text-align: center; line-height: 1.1;
}
.gstreak span { display: block; font-size: 9px; letter-spacing: .1em; text-transform: uppercase; font-weight: 600; }

.pips { display: flex; gap: 4px; margin-bottom: 12px; }
.pips i { flex: 1; height: 4px; border-radius: 2px; background: var(--rule); }
.pips i.done { background: var(--teal); }
.pips i.now { background: var(--navy); }

/* --- timer ring --- */
.ring { width: 44px; height: 44px; flex: none; transform: rotate(-90deg); }
.ringbg { fill: none; stroke: rgba(255,255,255,.55); stroke-width: 4; }
.prompt .ringbg { stroke: var(--rule); }
.ringfg {
  fill: none; stroke: var(--teal); stroke-width: 4; stroke-linecap: round;
  transition: stroke-dashoffset .1s linear;
}
.ringfg.low { stroke: var(--bad); }
.ringhold { position: absolute; top: 10px; right: 10px; }
.ringhold .ringbg { stroke: rgba(255,255,255,.5); }
.ringhold .ringfg { stroke: #fff; }
.ringhold .ringfg.low { stroke: #ff9a7a; }

/* --- name-the-face --- */
.photowrap { position: relative; }
/* Capped on WIDTH, not height, and centred.

   width:100% with max-height:400px looked right on a phone and wrong on a
   desktop, which is exactly how it was reported. On a narrow viewport the box
   stays portrait and the aspect-ratio holds. On a wide one the width runs to
   the full card, max-height clamps before the ratio can, and the box turns
   landscape - roughly 560x400 - so a portrait photo fitted to 400px of height
   sits marooned in the middle with bars either side.

   Capping the width keeps the box the same portrait shape at every size, so
   the photo fills it on desktop the way it already did on mobile. */
.game-photo {
  width: 100%; max-width: 340px; margin-inline: auto;
  aspect-ratio: 4 / 4.4; border-radius: var(--r);
  overflow: hidden; border: 1px solid var(--rule); background: var(--hover);
  display: grid; place-items: center; position: relative;
}
/* contain, NOT cover. cover fills the box by cropping whatever does not fit,
   and school photos are portrait with the head near the top, so the crop took
   the top of the head off - in a game whose entire question is "who is this",
   which is the one thing that must never be trimmed. contain letterboxes
   instead, and the background above already matches the frame. */
.game-photo img { width: 100%; height: 100%; object-fit: contain; display: block; }

.answers { display: grid; gap: 8px; grid-template-columns: 1fr 1fr; margin-top: 12px; }
@media (max-width: 480px) { .answers { grid-template-columns: 1fr; } }
.answers button {
  font: inherit; font-size: 14.5px; cursor: pointer; min-height: var(--tap);
  border-radius: 6px; border: 1px solid var(--rule-hi); background: var(--surface); color: var(--navy);
  padding: 10px 12px; transition: background .1s ease, transform .08s ease;
}
.answers button:hover { background: var(--hover); }
.answers button:active { transform: scale(.985); }
.answers button.right { background: #e7f2f0; border-color: var(--teal); color: var(--teal-lo); font-weight: 700; }
.answers button.wrong { background: #f9ecec; border-color: var(--bad); color: var(--bad); }
.answers button:disabled { cursor: default; opacity: .85; }

/* --- find-the-face --- */
.prompt {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r);
  padding: 12px 14px; margin-bottom: 12px;
}
.promptlabel {
  font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); font-weight: 600;
}
.promptname {
  flex: 1; font-family: var(--display); font-weight: 600; font-size: 20px;
  color: var(--navy); line-height: 1.15;
}
/* The board is CENTRED AND CAPPED, and the cap tightens as the grid grows.
   Nine portrait tiles at full column width ran past the fold, which turns a
   line-up you are meant to scan into a page you have to scroll. */
.facegrid { display: grid; gap: 8px; margin: 0 auto; }
.facegrid.g4 { grid-template-columns: repeat(2, 1fr); max-width: 420px; }
.facegrid.g6 { grid-template-columns: repeat(3, 1fr); max-width: 500px; }
/* Nine SQUARE tiles in three columns, so the board's height tracks its width —
   which means capping the width by the space actually left below the header
   and prompt is what keeps the whole line-up on screen. Measured: the chrome
   above it costs about 330px. */
.facegrid.g9 { grid-template-columns: repeat(3, 1fr); max-width: min(420px, calc(100vh - 330px)); }
.facepick {
  position: relative; padding: 0; border: 2px solid var(--rule); border-radius: var(--r);
  overflow: hidden; cursor: pointer; background: var(--hover); aspect-ratio: 4 / 4.4;
  transition: transform .1s ease, border-color .12s ease;
}
/* Squarer as the board fills up: the crop still centres on the face. */
.facegrid.g6 .facepick { aspect-ratio: 4 / 4.2; }
.facegrid.g9 .facepick { aspect-ratio: 1 / 1; }
/* Same reason as .game-photo img: a cropped face is the one thing this game
   cannot afford, and g9 is a 1/1 square against portrait source photos, which
   is where the crop bites hardest. */
.facepick img { width: 100%; height: 100%; object-fit: contain; display: block; }
.facepick:hover { transform: translateY(-2px); border-color: var(--teal); }
.facepick:disabled { cursor: default; }
.facepick .tick {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 34px; font-weight: 700; opacity: 0; transition: opacity .15s ease;
}
.facepick.right { border-color: var(--teal); }
.facepick.right .tick { opacity: 1; background: rgba(14,110,105,.55); color: #fff; }
.facepick.right .tick::after { content: '✓'; }
.facepick.wrong { border-color: var(--bad); }
.facepick.wrong .tick { opacity: 1; background: rgba(164,52,28,.5); color: #fff; }
.facepick.wrong .tick::after { content: '✕'; }

/* --- feedback --- */
.scorepop {
  position: absolute; top: 10px; left: 12px;
  font-family: var(--display); font-weight: 700; font-size: 24px; color: #fff;
  animation: pop .85s ease-out forwards; pointer-events: none;
  text-shadow: 0 2px 6px rgba(0,0,0,.45);
}
.answers .scorepop, .facegrid .scorepop { color: var(--teal-lo); text-shadow: 0 1px 0 #fff; top: -6px; }
@keyframes pop {
  0% { opacity: 0; transform: translateY(8px) scale(.8); }
  25% { opacity: 1; transform: translateY(0) scale(1.12); }
  100% { opacity: 0; transform: translateY(-20px) scale(1); }
}
.game.playing { position: relative; }
.flash-ok::before, .flash-bad::before {
  content: ''; position: absolute; inset: -8px; border-radius: 14px;
  pointer-events: none; animation: flash .5s ease-out forwards;
}
.flash-ok::before { box-shadow: 0 0 0 3px var(--teal) inset; }
.flash-bad::before { box-shadow: 0 0 0 3px var(--bad) inset; }
@keyframes flash { from { opacity: .85; } to { opacity: 0; } }

/* --- results --- */
.medal { display: flex; align-items: center; gap: 16px; }
.medal svg { width: 76px; height: 76px; flex: none; }
.lbmode { font-size: 11px; color: var(--muted); margin-right: 8px; }

@media (prefers-reduced-motion: reduce) {
  .modecard, .levelcard, .facepick, .answers button { transition: none; }
  .scorepop, .flash-ok::before, .flash-bad::before, .gstat b.bump { animation: none; }
}

/* --- progress: the mastery card, and the wall behind it ---

   Five bands, five colours, used identically on the summary dots, the tile
   borders and the bars — so a colour means the same thing everywhere on the
   screen and the wall can be read without reading any of the words. Amber has
   no token because the palette has no warning colour; it matches the one the
   calendar already uses for recess. */
.game.wide { max-width: 980px; }

.progcard {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  font: inherit; cursor: pointer; padding: 14px 16px;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.progcard:hover { border-color: var(--teal); box-shadow: 0 6px 18px rgba(14,39,67,.08); }
.progbig {
  font-family: var(--display); font-weight: 700; font-size: 30px; color: var(--teal-lo);
  line-height: 1; font-variant-numeric: tabular-nums; flex: none;
}
.progbig i { font-style: normal; font-size: 15px; }
.progtext { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.progtext b { font-size: 14.5px; color: var(--navy); }
.progtext span { font-size: 12.5px; color: var(--muted); }
.proggo { font-size: 12.5px; font-weight: 600; color: var(--teal-lo); flex: none; }
@media (max-width: 520px) { .proggo { display: none; } }

.drillbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: #e7f2f0; border: 1px solid var(--teal); border-radius: var(--r);
  padding: 9px 13px; margin-bottom: 16px; font-size: 13.5px; color: var(--teal-lo);
}
.drillbar button {
  font: inherit; font-size: 12.5px; cursor: pointer; background: none;
  border: 0; padding: 0; color: var(--teal-lo); text-decoration: underline;
}

.walltools {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin: 14px 0 12px;
}
.wallcounts { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; font-size: 12.5px; color: var(--muted); }
.wallcounts .dot {
  width: 9px; height: 9px; border-radius: 50%; display: inline-block;
  background: var(--band, var(--rule-hi)); margin: 0 4px 0 10px;
}
.wallcounts .dot:first-child { margin-left: 0; }
.dot.solid { --band: var(--teal); }
.dot.good  { --band: #4b8f6b; }
.dot.shaky { --band: #c98a2e; }
.dot.weak  { --band: var(--bad); }
.dot.new   { --band: var(--rule-hi); }
.wallbtns { display: flex; gap: 6px; }

.wall {
  display: grid; gap: 10px; margin-top: 14px;
  grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
}
.wtile {
  display: flex; flex-direction: column; gap: 3px;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r);
  padding: 8px 8px 10px; overflow: hidden;
  /* The band shows as a stripe down the left rather than a full border: at
     this tile size a coloured outline around a photo reads as an alert, and
     most of a healthy wall is meant to look calm. */
  border-left: 3px solid var(--band, var(--rule-hi));
}
.wtile.solid { --band: var(--teal); }
.wtile.good  { --band: #4b8f6b; }
.wtile.shaky { --band: #c98a2e; }
.wtile.weak  { --band: var(--bad); }
.wtile.new   { --band: var(--rule-hi); }
.wphoto {
  display: block; width: 100%; aspect-ratio: 4 / 4.4; border-radius: 5px;
  overflow: hidden; background: var(--hover);
}
.wphoto img { width: 100%; height: 100%; object-fit: contain; display: block; }
.wname {
  font-size: 13px; font-weight: 600; color: var(--navy); margin-top: 5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wmeta { font-size: 11px; color: var(--muted); }
.wbar { display: block; height: 4px; border-radius: 2px; background: var(--rule); overflow: hidden; margin-top: 3px; }
.wbar i { display: block; height: 100%; background: var(--band, var(--rule-hi)); }

.lb { border: 1px solid var(--rule); border-radius: var(--r); overflow: hidden; background: var(--surface); }
.lb .lbrow { display: flex; gap: 10px; align-items: baseline; padding: 8px 14px; border-bottom: 1px solid var(--rule); font-size: 14px; }
.lb .lbrow:last-child { border-bottom: 0; }
.lb .rank { color: var(--muted); font-variant-numeric: tabular-nums; width: 26px; }
.lb .lbnm { flex: 1; color: var(--navy); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb .pts { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--teal-lo); }
.lb .lbrow.me { background: #f2f7f6; }

/* ---------------------------------------------------------------------------
   PRINT — its own DOM. Screen chrome vanishes; only #printroot prints.
   --------------------------------------------------------------------------- */
#printroot { display: none; }

@media print {
  @page { margin: 13mm 11mm; }
  body { background: #fff; font-size: 11pt; }
  .topbar, .periodbar, main { display: none !important; }
  #printroot { display: block !important; }

  /* PURELY TYPOGRAPHIC, no crest. Two attempts at aligning the mark against
     this text block both read as a misprint: the menorah is tall and narrow,
     the block is short and wide, and no vertical alignment makes those agree
     without a rule or a panel to sit in. The school name set in caps is the
     identity here; the logo earns its place on screen, not on a class list. */
  .p-head { border-bottom: 2px solid #000; padding-bottom: 6px; margin-bottom: 12px; }
  .p-headtext { min-width: 0; }
  .p-school { font-size: 10pt; letter-spacing: .14em; text-transform: uppercase; }
  .p-title { font-size: 17pt; font-weight: 700; margin: 2px 0 4px; }
  .p-meta { font-size: 9.5pt; display: flex; gap: 18px; flex-wrap: wrap; }
  .p-meta b { font-weight: 600; }

  table.p-table { width: 100%; border-collapse: collapse; }
  table.p-table th {
    text-align: left; font-size: 8.5pt; text-transform: uppercase; letter-spacing: .07em;
    border-bottom: 1px solid #000; padding: 4px 6px;
  }
  /* 9.5pt, not 10.5. Print resolution is far more forgiving than a screen and
     this is read at arm's length rather than studied, so the point size buys
     both width and rows at no real cost to legibility. */
  table.p-table td { padding: 5px 6px; border-bottom: 1px solid #bbb; font-size: 10pt; }
  table.p-table td.num { width: 26px; color: #555; font-variant-numeric: tabular-nums; }
  table.p-table tr { page-break-inside: avoid; }
  table.p-table thead { display: table-header-group; }
  .p-tick { width: 24px; border-left: 1px solid #bbb; }

  /* Eight columns is a lot for portrait, and the two that blow the layout are
     the phone numbers, which must never wrap - a number broken across two
     lines is worse than a truncated name, because half a phone number reads as
     a whole one. Slightly smaller and tabular so the digits line up down the
     column, which is how somebody scans for the one they want. */
  .p-tel { white-space: nowrap; font-size: 9.5pt; font-variant-numeric: tabular-nums; }

  /* The second-household row. Hairline top border rather than the usual rule,
     so it reads as attached to the boy above it and not as the next boy - the
     one thing this row must never be mistaken for. */
  /* Tighter rows once the table is landscape. The old padding was sized for a
     portrait sheet where wrapping set the row height anyway; with the wrapping
     gone it was just air, and air is rows per page. */
  table.p-table td { padding: 3px 6px; }
  tr.p-c2 td { border-top: 0; border-bottom: 1px solid #bbb; padding-top: 0; color: #333; }
  tr.p-c2 td:not(.p-c2label) { font-size: 9.5pt; }
  .p-c2label { font-size: 8pt; font-style: italic; color: #666; padding-left: 10px !important; }
  .p-foot { margin-top: 10px; font-size: 8.5pt; color: #555; }

  /* ATTENDANCE. The boxes are the point of this sheet, so the name column is
     given a fixed share instead of being allowed to take whatever it likes.

     Auto table layout sizes columns to their content, so a column of full
     names claimed most of the width and squeezed ten 24px boxes into the last
     third of the page - narrow enough to be awkward to tick and nowhere near
     the pen. table-layout: fixed makes the percentage below authoritative, so
     the boxes start near the one-third mark.

     The boxes keep their 24px width - the reclaimed space buys MORE of them,
     not fatter ones, since the point is more days before a fresh sheet. The
     count lives in printRoster. */
  table.p-table.attend { table-layout: fixed; }
  table.p-table.attend .p-name { width: 32%; overflow: hidden; text-overflow: ellipsis; }

  /* Photo sheet: SIX across, five down = 30 per page.
     Five columns only fitted four rows (20), because the cell is taller than
     it is wide and the row height, not the column count, is what runs out.
     Narrower columns shrink the height with them: on Letter, 6 columns across
     ~7.6in of content leaves ~1.2in-wide cells, ~1.35in of photo plus a
     caption — about 1.65in a row, and five of those fit under the header. */
  .p-photos { display: grid; grid-template-columns: repeat(6, 1fr); gap: 3mm 2.5mm; }
  .p-photo { text-align: center; page-break-inside: avoid; }
  /* contain here too. A photo sheet exists so somebody can put a name to a
     face, so a cropped forehead defeats the sheet as surely as it defeats the
     game. The cells are a fixed aspect against portrait originals, so cover
     was trimming every one of them. */
  .p-photo img {
    width: 100%; aspect-ratio: 4 / 4.5; object-fit: contain;
    border: 1px solid #999; border-radius: 2px;
  }
  .p-photo .cap { font-size: 7pt; line-height: 1.15; margin-top: 0.8mm; }

  /* Month grid on paper: the screen chrome (nav arrows, scroll box) goes,
     the grid stays and is allowed to be tall. */
  .pcal .mhead .btn { display: none; }
  .pcal .mhead { justify-content: flex-start; margin-bottom: 8px; }
  .pcal .mhead h2 { font-size: 14pt; text-align: left; min-width: 0; }
  .pcal .mgridwrap { overflow: visible; }
  .pcal .mgrid { min-width: 0; border: 1px solid #999; gap: 0; background: #fff; }
  .pcal .mdow { background: #f0f0f0; border: 1px solid #ccc; font-size: 7.5pt; padding: 3px; }
  .pcal .mcell {
    border: 1px solid #ccc; min-height: 78px; padding: 3px 4px;
    page-break-inside: avoid;
  }
  .pcal .mcell.out { background: #fafafa; }
  .pcal .mcell.recess { background: #f4ece0; }
  .pcal .mcell.today { box-shadow: none; }
  .pcal .mnum { font-size: 8pt; }
  .pcal .mev {
    font-size: 6.8pt; padding: 1px 3px; white-space: normal;
    border-left-width: 2px; background: #f2f2f2; color: #000;
  }
  .pcal .mmore { font-size: 6.5pt; }
}

/* --- usage screen ---------------------------------------------------------
   Counts, not a log reader. Everything here is sized so the comparison people
   actually make - this against that - can be made without reading numbers. */
.ustats { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 10px; margin: 14px 0 4px; }
.ustat { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r); padding: 12px 14px; }
.ustat b { display: block; font-size: 26px; line-height: 1.1; color: var(--navy); font-variant-numeric: tabular-nums; }
.ustat span { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }

.ucols { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; margin-top: 18px; }
@media (max-width: 720px) { .ucols { grid-template-columns: 1fr; } }

.ubars { display: grid; gap: 5px; }
.ubar { display: grid; grid-template-columns: minmax(90px, 1.4fr) 2fr auto; gap: 8px; align-items: center; font-size: 13.5px; }
.ubl { color: var(--navy); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ubt { background: var(--hover); border-radius: 3px; height: 9px; overflow: hidden; }
.ubt i { display: block; height: 100%; background: var(--teal, #0e6e69); border-radius: 3px; }
.ubn { color: var(--muted); font-variant-numeric: tabular-nums; min-width: 34px; text-align: right; }

/* A clickable bar is a <button>, which arrives with a border, a background,
   centred text and its own font -- all of which has to come back off, or the
   clickable rows stop lining up with the ones that are not. Only the
   affordance is added on top. */
button.ubar {
  width: 100%; text-align: left; font: inherit; font-size: 13.5px;
  background: none; border: 0; cursor: pointer;
  padding: 2px 4px; margin: -2px -4px; border-radius: 5px;
}
button.ubar:hover { background: var(--hover); }
button.ubar:hover .ubl { color: var(--teal); text-decoration: underline; }

/* One bar per day across the whole window, empty days included - a quiet week
   is the finding, and a chart that omits it draws a smooth line over the gap. */
.udays { display: flex; align-items: flex-end; gap: 2px; height: 54px;
         background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r); padding: 6px; }
.udays i { flex: 1 1 auto; min-width: 2px; background: var(--teal, #0e6e69); opacity: .75; border-radius: 1px; }

.utable { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.utable th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
             color: var(--muted); border-bottom: 1px solid var(--rule); padding: 6px 8px; }
.utable td { padding: 6px 8px; border-bottom: 1px solid var(--rule); vertical-align: top; }
.uwhen { white-space: nowrap; color: var(--muted); font-variant-numeric: tabular-nums; }
.udetail { color: var(--muted); }
.ubadge { font-size: 10.5px; background: var(--hover); border-radius: 3px; padding: 1px 5px; color: var(--muted); }

/* ===========================================================================
   EVALUATIONS  --  the fill screen
   ===========================================================================
   The grid is the whole screen and it is used twenty-five times in a sitting,
   so targets are large, the keyboard hint is permanent, and the hovered cell
   lights a trail back to the start of the row. The trail is not decoration:
   it is what makes a row of five cells read as ONE scale rather than five
   independent checkboxes, which is the difference between ticking and rating.
   =========================================================================== */

.evr-list { display: grid; gap: 10px; }
.evr-card {
  display: flex; align-items: center; gap: 16px; width: 100%;
  padding: 14px 18px; text-align: left; cursor: pointer;
  background: var(--card, #fff); border: 1px solid var(--line, #e3e3e8);
  border-radius: 10px; font: inherit;
}
.evr-card:hover { border-color: var(--accent, #2a6ef0); }
.evr-card-main { flex: 1; display: flex; flex-direction: column; gap: 3px; }
.evr-sub { font-size: 0.85rem; color: var(--muted, #6b6b76); }
.evr-late { color: #b3261e; font-weight: 600; }
.evr-count { font-size: 1.5rem; font-weight: 700; line-height: 1; text-align: center; }
.evr-count span { display: block; font-size: 0.7rem; font-weight: 400; color: var(--muted, #6b6b76); }

.ev-top { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 14px; }
.ev-back { font-size: 0.9rem; }
.ev-progress { flex: 1; min-width: 180px; display: flex; align-items: center; gap: 10px; }
.ev-progress span { font-size: 0.82rem; color: var(--muted, #6b6b76); white-space: nowrap; }
.ev-bar { flex: 1; height: 6px; border-radius: 3px; background: var(--line, #e3e3e8); overflow: hidden; }
.ev-bar i { display: block; height: 100%; background: var(--accent, #2a6ef0); transition: width .25s ease; }
.ev-saved { font-size: 0.82rem; color: var(--muted, #6b6b76); min-width: 60px; text-align: right; }
.ev-saved .bad { color: #b3261e; }

.ev-returned {
  padding: 10px 14px; border-radius: 8px; margin-bottom: 12px;
  background: #fff6e0; border: 1px solid #e8cf92; font-size: 0.9rem;
}
.ev-card { background: var(--card, #fff); border: 1px solid var(--line, #e3e3e8);
  border-radius: 12px; padding: 20px; }
.ev-who { display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; }
.ev-who h2 { margin: 0; font-size: 1.5rem; }
.ev-who p { margin: 2px 0 0; color: var(--muted, #6b6b76); font-size: 0.9rem; }
.ev-nav { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.ev-nav span { font-size: 0.85rem; color: var(--muted, #6b6b76); }

.ev-grid { width: 100%; border-collapse: separate; border-spacing: 0; }
.ev-grid th { font-weight: 600; font-size: 0.85rem; padding: 8px 10px; text-align: center; }
.ev-grid tbody th { text-align: left; font-weight: 400; font-size: 0.95rem; width: 40%; }
.ev-key {
  display: inline-block; margin-right: 6px; min-width: 18px; padding: 1px 4px;
  border-radius: 4px; background: var(--line, #e3e3e8);
  font-size: 0.72rem; color: var(--muted, #6b6b76);
}
.ev-cell {
  position: relative; text-align: center; cursor: pointer; height: 54px;
  border-top: 1px solid var(--line, #e3e3e8); transition: background .12s ease;
}
.ev-cell:focus-visible { outline: 2px solid var(--accent, #2a6ef0); outline-offset: -2px; }
.ev-dot {
  display: inline-block; width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--line, #cfcfd6); transition: all .15s ease;
}
.ev-cell.on .ev-dot {
  border-color: var(--accent, #2a6ef0); background: var(--accent, #2a6ef0);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, #2a6ef0) 18%, transparent);
}
/* THE TRAIL. Hovering a cell tints it and every cell before it in the row,
   fading toward the start -- so the row reads as a scale being filled to a
   point rather than as five separate boxes. Pure CSS via :has, no JS. */
.ev-grid tr:hover .ev-cell { background: transparent; }
.ev-cell:hover, .ev-cell:has(~ .ev-cell:hover) {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--accent, #2a6ef0) 4%, transparent),
    color-mix(in srgb, var(--accent, #2a6ef0) 14%, transparent));
}
.ev-cell:hover .ev-dot { border-color: var(--accent, #2a6ef0); }

.ev-notes { display: grid; gap: 8px; margin-top: 16px; }
.ev-notes label { display: grid; grid-template-columns: 40% 1fr; align-items: center; gap: 10px; }
.ev-notes span { font-size: 0.88rem; color: var(--muted, #6b6b76); }
.ev-note-big { display: block; margin-top: 16px; }
.ev-note-big span { display: block; font-size: 0.88rem; font-weight: 600; margin-bottom: 5px; }
.ev-note-big em { font-weight: 400; color: var(--muted, #6b6b76); }
.ev-note-big textarea { width: 100%; }
.ev-note-big.internal textarea { background: #fbfbe8; }

.ev-actions { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
.ev-actions .primary { margin-left: auto; }
.ev-preview { width: 100%; height: 900px; border: 1px solid var(--line, #e3e3e8);
  border-radius: 10px; margin-top: 14px; background: #fff; }
.ev-hint { margin-top: 12px; font-size: 0.8rem; color: var(--muted, #6b6b76); }
.ev-hint kbd {
  padding: 1px 5px; border-radius: 4px; border: 1px solid var(--line, #cfcfd6);
  background: var(--card, #fff); font-size: 0.75rem;
}

/* Below the tablet break the grid stops being a grid. Five columns of 54px
   cells do not fit a phone, and a rebbe on a phone is the case the paper
   accommodation was meant to cover -- so the row becomes a stack of full-width
   buttons, which is a better touch target than the table ever was. */
@media (max-width: 700px) {
  .ev-grid, .ev-grid thead, .ev-grid tbody, .ev-grid tr, .ev-grid th, .ev-grid td { display: block; }
  .ev-grid thead { display: none; }
  .ev-grid tr { margin-bottom: 18px; }
  .ev-grid tbody th { width: auto; padding: 0 0 8px; font-weight: 600; }
  .ev-cell {
    height: auto; padding: 12px 14px; border: 1px solid var(--line, #e3e3e8);
    border-radius: 8px; margin-bottom: 6px; text-align: left; display: flex;
    align-items: center; gap: 10px;
  }
  .ev-cell::after { content: attr(data-label); font-size: 0.95rem; }
  .ev-cell:has(~ .ev-cell:hover) { background: transparent; }
  .ev-notes label { grid-template-columns: 1fr; }
  .ev-hint { display: none; }
}

/* ---------------------------------------------------------------------------
   EVALUATIONS  --  the review queue
   ---------------------------------------------------------------------------
   Dense on purpose. The menahel is reading four hundred rows, so the row is
   the unit and the page is a table rather than a stack of cards. The rating
   dots are what make a column scannable: a shiur that is all Excellent has a
   visible SHAPE, which five columns of words do not.
   --------------------------------------------------------------------------- */

.evr-row { display: flex; align-items: center; gap: 10px; }
.evr-row .evr-card { flex: 1; }
.evr-review { white-space: nowrap; font-size: 0.85rem; }

.rv-group { margin: 22px 0; }
.rv-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 8px 2px; border-bottom: 2px solid var(--line, #e3e3e8);
}
.rv-head strong { font-size: 1.05rem; }
.rv-head span { color: var(--muted, #6b6b76); font-size: 0.85rem; }
.rv-head .primary { margin-left: auto; }

.rv-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.rv-table th {
  text-align: left; font-size: 0.75rem; font-weight: 600; padding: 8px 6px;
  color: var(--muted, #6b6b76); white-space: nowrap;
}
.rv-table td { padding: 7px 6px; border-top: 1px solid var(--line, #eee); vertical-align: top; }
.rv-table tr:hover td { background: color-mix(in srgb, var(--accent, #2a6ef0) 4%, transparent); }
.rv-decided td { opacity: 0.55; }

/* The name is the way into the full page, so it looks like a control at rest
   rather than only on hover -- a hairline underline reads as texture in a
   dense table and as a link the moment somebody looks for one. */
.rv-name {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  text-align: left; border-bottom: 1px solid var(--line, #d7d7de);
}
.rv-name:hover { border-bottom-color: var(--accent, #2a6ef0); }
.rv-paper {
  margin-left: 6px; font-size: 0.68rem; padding: 1px 5px; border-radius: 3px;
  background: #f0ead6; color: #6b5d2f;
}

.rv-dots { display: inline-flex; gap: 3px; align-items: center; }
.rv-dots i {
  width: 7px; height: 7px; border-radius: 50%; display: block;
  background: var(--line, #dcdce2);
}
.rv-dots i.on { background: var(--accent, #2a6ef0); transform: scale(1.5); }
.rv-missing { color: #b3261e; font-weight: 700; }

.rv-note { max-width: 26rem; white-space: pre-wrap; }
.rv-priv {
  margin-top: 4px; padding: 4px 6px; border-radius: 4px;
  background: #fbfbe8; font-size: 0.82rem;
}
/* A comment cell that can be written in says so before anybody clicks it --
   the marks in the same row carry their own affordance for the same reason. */
.rv-note-edit { cursor: text; border-radius: 4px; }
.rv-note-edit:hover {
  background: var(--hover, #f4f4f7);
  box-shadow: inset 0 0 0 1px var(--line, #e3e3e8);
}
.rv-note-box {
  width: 100%; font: inherit; padding: 6px 8px; resize: vertical;
  border: 1px solid var(--line, #d7d7de); border-radius: 4px;
  background: var(--surface, #fff);
}
.rv-note-box + .rv-note-box { margin-top: 6px; }
.rv-note-private { background: #fbfbe8; }
.rv-note-acts { display: flex; gap: 6px; margin-top: 6px; }

.rv-back { font-size: 0.8rem; }

.rv-chip { font-size: 0.72rem; padding: 2px 7px; border-radius: 10px; white-space: nowrap; }
.rv-chip.ok { background: #e4f4e8; color: #1d7a3f; }
.rv-chip.warn { background: #fdf2dc; color: #8a6d1f; }
.rv-chip.bad { background: #fbe6e4; color: #b3261e; }
.rv-chip.muted { background: var(--line, #eee); color: var(--muted, #6b6b76); }

/* The action bar only exists once something is selected. A permanently visible
   "Approve 0" is a button that does nothing most of the time. */
.rv-bar {
  position: sticky; bottom: 0; display: none; gap: 12px; align-items: center;
  padding: 12px 16px; margin-top: 18px;
  background: var(--card, #fff); border: 1px solid var(--line, #e3e3e8);
  border-radius: 10px; box-shadow: 0 -2px 14px rgba(0, 0, 0, 0.08);
}
.rv-bar.on { display: flex; }
.rv-bar .primary { margin-left: auto; }

@media (max-width: 860px) {
  /* The criteria columns are the first thing to go: on a narrow screen the
     name, the comment and the decision are what a reviewer needs, and the
     ratings are one tap away on the full page. */
  .rv-table th:nth-child(n+3):nth-last-child(n+3),
  .rv-table td:nth-child(n+3):nth-last-child(n+3) { display: none; }
  .rv-note { max-width: none; }
}

/* The send bar on the review queue. Deliberately separate from the selection
   bar above it: that one acts on ticked rows, this one acts on everything
   approved, and one button meaning two things by context is how somebody mails
   a whole round intending to mail one shiur. */
.rv-send {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin-top: 16px; padding: 14px 18px; border-radius: 10px;
  background: var(--card, #fff); border: 1px solid var(--line, #e3e3e8);
}
.rv-send > div { flex: 1; min-width: 240px; }
.rv-send .muted { color: var(--muted, #6b6b76); font-weight: 400; }
.rv-sendresult {
  margin-top: 12px; padding: 12px 16px; border-radius: 8px;
  background: var(--card, #fff); border: 1px solid var(--line, #e3e3e8);
}
.rv-sendresult ul { margin: 8px 0 0; padding-left: 20px; }
.rv-sendresult li { color: #b3261e; font-size: 0.88rem; }

/* The keying screen: scan on the left, the same grid on the right. Side by side
   because the office is copying ticks across, and a layout that makes them
   scroll between the two is a layout that produces mistakes. */
.k-split { display: grid; grid-template-columns: minmax(320px, 46%) 1fr; gap: 18px; align-items: start; }
.k-scan { position: sticky; top: 12px; }
.k-scan iframe {
  width: 100%; height: 78vh; border: 1px solid var(--line, #e3e3e8);
  border-radius: 10px; background: #fff;
}
.k-form { background: var(--card, #fff); border: 1px solid var(--line, #e3e3e8);
  border-radius: 12px; padding: 18px; }
/* Below the tablet break they stack, and the scan stops being sticky -- a
   sticky half-screen image on a phone leaves no room for the grid. */
@media (max-width: 900px) {
  .k-split { grid-template-columns: 1fr; }
  .k-scan { position: static; }
  .k-scan iframe { height: 52vh; }
}

/* What the reader made of a scanned page. Not an alert: on a good scan this is
   reassurance, and only the uncertain rows get the warning treatment. */
.k-note {
  margin: 0 0 12px; padding: 10px 12px; border-radius: 8px;
  background: var(--bg-off, #f5f5f7); font-size: 0.88rem;
}
.k-note.warn { background: #fff6e0; border: 1px solid #e8cf92; }

/* A demo round says so, loudly and on every screen. Red rather than the brand
   colour on purpose: this is the one badge that must never read as decoration.
   The bar repeats per screen rather than appearing once on entry -- the moment
   it matters is not arrival, it is the Send button twenty minutes later. */
.demo-bar {
  margin: 0 0 14px; padding: 9px 14px; border-radius: 8px;
  background: #b3261e; color: #fff; font-size: 0.86rem; font-weight: 600;
  letter-spacing: 0.02em;
}
/* ===========================================================================
   THE IMPORTED-SCAN CARD
   ===========================================================================
   A stack of paper has come in and is waiting on somebody. That is a STATE the
   round is in, not a button -- and it used to be a sentence in the controls row
   with the action tucked between a dropdown and two file pickers, so the one
   thing anybody opens this tab to do looked like the least important thing on
   it.

   Built from the theme tokens rather than the brand colours directly: this app
   ships several palettes and a card that hardcodes brown is a card that is
   wrong in three of them. The artwork is drawn rather than photographed because
   it has to say "pages, stacked, ticked" at 54px and keep saying it on a
   projector. */
.scancard {
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
  margin: 16px 0 0; padding: 20px 22px;
  border-radius: 14px;
  border: 1px solid var(--rule);
  background:
    radial-gradient(120% 150% at 0% 0%, var(--accent-soft) 0%, transparent 62%),
    var(--surface);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 10px 26px -14px rgba(16, 24, 40, 0.22);
}

.scancard-art { flex: 0 0 auto; width: 76px; height: 76px; }
.scancard-art svg { display: block; width: 100%; height: 100%; }

.scancard-body { flex: 1 1 280px; min-width: 0; }
.scancard-body h3 {
  margin: 0 0 5px; font-size: 1.1rem; line-height: 1.25; color: var(--navy);
  letter-spacing: -0.01em;
}
.scancard-body p { margin: 0; font-size: 0.85rem; line-height: 1.5; color: var(--muted); }

.scancard-acts { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Quiet until it is hovered. It has to be findable without being the thing the
   eye lands on next to Review. */
.btn.danger-ghost { background: none; border: 1px solid var(--rule); color: var(--muted); }
.btn.danger-ghost:hover {
  border-color: var(--maroon); color: var(--maroon);
  background: color-mix(in srgb, var(--maroon) 7%, transparent);
}

@media (max-width: 560px) {
  .scancard { gap: 14px; padding: 16px; }
  .scancard-art { width: 52px; height: 52px; }
  .scancard-acts { width: 100%; }
  .scancard-acts .btn { flex: 1 1 auto; text-align: center; }
}

/* A boy taken out of the cycle. Struck through and dimmed rather than removed:
   an exclusion you cannot see is how somebody discovers months later that a boy
   got no report all year. */
.sb.sb-out { opacity: 0.62; }
.sb.sb-out > span:first-child { text-decoration: line-through; }
.sb .sb-act { margin-left: auto; padding: 1px 8px; font-size: 0.72rem; }
.sb:hover .sb-act { opacity: 1; }
.sb .sb-act { opacity: 0.45; transition: opacity .12s; }

/* A signature, shown at a size somebody can actually judge. White behind it
   because the ink is cut out with a transparent background, and a signature on
   a grey table row is harder to read than one on paper. */
.sig-img {
  display: block; height: 44px; max-width: 260px;
  background: #fff; border: 1px solid var(--rule); border-radius: 6px;
  padding: 3px 6px; object-fit: contain;
}

/* A control that is working rather than waiting to be pressed. It replaces the
   button in place rather than sitting beside it, so the thing somebody reaches
   for is simply not there to be pressed a second time -- which is what three
   consecutive imports of the same stack came down to. */
.btn.is-busy {
  cursor: progress; opacity: 0.75; pointer-events: none;
  font-variant-numeric: tabular-nums;
}

/* The fill-for-a-rebbe banner. Its own bar rather than a .card, which is a
   container for content -- this is a statement about the mode the screen is in,
   and it was reading as a stray paragraph with a button loose inside it. Laid
   out as a row so the sentence and the way out cannot drift apart at any
   width. */
.pivot-bar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 0 0 16px; padding: 11px 16px; border-radius: 10px;
  /* A wash of the brand teal rather than a new token: it reads correctly
     against both themes without anything else having to know about it. */
  background: rgba(14, 110, 105, 0.07);
  border: 1px solid var(--teal);
  border-left-width: 4px;
}
.pivot-bar .pv-txt { flex: 1 1 260px; min-width: 0; font-size: 0.86rem; line-height: 1.45; }
.pivot-bar .pv-who { font-weight: 700; color: var(--navy); }
/* Said quietly but said: the sentence people skip is the one that matters if
   they leave without handing in. */
.pivot-bar .pv-note { display: block; margin-top: 2px; color: var(--muted); font-size: 0.8rem; }
.pivot-bar .btn { flex: 0 0 auto; }

@media (prefers-color-scheme: dark) {
  .pivot-bar { background: rgba(14, 110, 105, 0.18); }
}

.demo-tag {
  align-self: center; margin-right: 10px; padding: 2px 8px; border-radius: 999px;
  background: #b3261e; color: #fff; font-size: 0.68rem; font-weight: 700;
  letter-spacing: 0.06em;
}

/* An <a class="btn"> is still a link, and links are underlined. Every button in
   these screens is one or the other depending on whether it navigates, and they
   have to look the same either way. */
a.btn, a.btn:hover, a.btn:visited { text-decoration: none; }

/* THE HEBREW FACE FOR THE UI, so a boy's name is one name rather than two
   typefaces. The report renderer already embeds its own faces; this is the
   screen, and it needs exactly one: Heebo, because it is the Hebrew companion
   to Roboto and so is metrically matched to Latin -- which is the whole problem
   with "<hebrew> Rosenthal" sitting on one line. Hebrew subset only, ~24KB,
   already on disk for the renderer. */
@font-face {
  font-family: 'Heebo';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/heebo-hebrew-400.woff2) format('woff2');
  unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
@font-face {
  font-family: 'Heebo';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(fonts/heebo-hebrew-700.woff2) format('woff2');
  unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* Listed FIRST so the Hebrew range wins for Hebrew characters; the unicode-range
   above means Latin still falls through to the UI font underneath. */
.ev-who h2, .ev-who p, .ev-grid tbody th, .rv-name, .k-form h2 {
  /* 'inherit' is NOT a valid entry in a font-family LIST -- the whole
     declaration is dropped, which is why the Hebrew stayed in the browser's
     default face while the surname beside it used the UI font. The real
     stack is named, with Heebo first: its unicode-range covers Hebrew only,
     so Latin falls straight through to var(--sans) underneath. */
  font-family: 'Heebo', var(--sans);
}

/* The review screen's reading modes. Colour comes from POSITION on the scale,
   never from the label -- the scale is config and can be three levels or eight,
   either way round. It is also never the only carrier: dots keep their
   position, words keep their text, and the title names the rating. */
.rv-view { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.rv-vbtn {
  background: none; border: 1px solid var(--line, #e3e3e8); border-radius: 6px;
  padding: 3px 10px; font: inherit; font-size: 0.78rem; cursor: pointer;
  color: var(--muted, #6b6b76);
}
.rv-vbtn.on { background: var(--card, #fff); color: var(--text); font-weight: 600; }
.rv-vcol { font-size: 0.78rem; color: var(--muted, #6b6b76); display: flex; gap: 4px; align-items: center; margin-left: 6px; }

.rv-word { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 0.82rem; white-space: nowrap; }
.rv-r1 { background: #d8f0dd; color: #14532d; }
.rv-r2 { background: #e6f2d5; color: #2f4712; }
.rv-r3 { background: #fdf0cd; color: #5a4304; }
.rv-r4 { background: #fde0cb; color: #6b3410; }
.rv-r5 { background: #fbd6d3; color: #7a1d16; }
/* On the dots the ramp tints the filled one only, so an uncoloured row still
   reads by position exactly as it did before. */
.rv-dots.rv-r1 i.on { background: #2e7d4f; }
.rv-dots.rv-r2 i.on { background: #6f9a2e; }
.rv-dots.rv-r3 i.on { background: #c79a10; }
.rv-dots.rv-r4 i.on { background: #d4712c; }
.rv-dots.rv-r5 i.on { background: #c0392b; }

.rv-acts { white-space: nowrap; display: flex; gap: 6px; justify-content: flex-end; }
.rv-edit { padding: 14px 16px; background: var(--bg-off, #f5f5f7); border-radius: 10px; }
.rv-edit-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.rv-edit-head .muted { flex: 1; font-size: 0.82rem; }

/* The rebbe picker beside Print blanks. Sized to the names it holds rather than
   stretched: it sits in a row of buttons and a full-width select would read as
   the main control on a card whose main control is the print. */
.rv-blank-sel {
  padding: 8px 10px; border-radius: 8px; border: 1px solid var(--line, #e3e3e8);
  background: var(--bg, #fff); color: var(--text); font: inherit; font-size: 0.88rem;
  max-width: 260px;
}

/* A mark you can change. Looks like the reading it already was -- the hint is
   the cursor and the hover, not a border round every cell, because a table of
   twenty-five boys with five bordered cells each is a grid of boxes. */
.rv-mark { cursor: pointer; }
.rv-mark:hover .rv-word { outline: 2px solid var(--accent, #2a6ef0); outline-offset: 1px; }
.rv-mark:hover .rv-dots { outline: 2px solid var(--accent, #2a6ef0); outline-offset: 3px; border-radius: 999px; }
.rv-mark-sel { font: inherit; font-size: 0.85rem; padding: 3px 6px; border-radius: 6px;
  border: 1px solid var(--accent, #2a6ef0); background: var(--bg, #fff); color: var(--text); }
.rv-saving { color: var(--muted, #6b6b76); }

/* The mark popover: the other choices, right under the one you clicked. */
.rv-mark { position: relative; }
.rv-pop {
  position: absolute; z-index: 40; top: 100%; left: 0; margin-top: 4px;
  display: flex; flex-direction: column; gap: 3px; padding: 6px;
  background: var(--card, #fff); border: 1px solid var(--line, #e3e3e8);
  border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.16); min-width: 140px;
}
.rv-pop-b {
  border: 0; border-radius: 999px; padding: 5px 12px; cursor: pointer;
  font: inherit; font-size: 0.84rem; text-align: left; background: var(--bg-off, #f5f5f7);
}
.rv-pop-b.on { outline: 2px solid var(--accent, #2a6ef0); }
.rv-pop-b:hover { filter: brightness(0.96); }

/* Display settings, behind a gear rather than across the top. */
.rv-view { position: relative; margin-left: auto; }
.rv-gear {
  background: none; border: 0; cursor: pointer; font-size: 1.05rem; line-height: 1;
  color: var(--muted, #6b6b76); padding: 4px 6px; border-radius: 6px;
}
.rv-gear:hover { background: var(--bg-off, #f5f5f7); color: var(--text); }
.rv-viewpop {
  position: absolute; right: 0; top: 100%; margin-top: 4px; z-index: 40;
  display: flex; align-items: center; gap: 6px; padding: 8px 10px; white-space: nowrap;
  background: var(--card, #fff); border: 1px solid var(--line, #e3e3e8);
  border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.16);
}

/* The wait while a PDF is rendered. Blocking on purpose: the result opens in a
   new tab, so a second click during the first would make a second tab and a
   second render. */
.ev-busy {
  position: fixed; inset: 0; z-index: 9000; display: flex;
  align-items: center; justify-content: center; background: rgba(0,0,0,0.28);
}
.ev-busy-card {
  background: var(--card, #fff); padding: 22px 26px; border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0,0,0,0.3); text-align: center;
  max-width: 320px; font-size: 0.95rem;
}
.ev-busy-sub { margin-top: 6px; font-size: 0.82rem; color: var(--muted, #6b6b76); }
.ev-spin {
  width: 26px; height: 26px; margin: 0 auto 12px; border-radius: 50%;
  border: 3px solid var(--line, #e3e3e8); border-top-color: var(--accent, #2a6ef0);
  animation: ev-spin 0.8s linear infinite;
}
@keyframes ev-spin { to { transform: rotate(360deg); } }
/* Somebody who has asked for less motion gets a pulse instead of a spinner. */
@media (prefers-reduced-motion: reduce) {
  .ev-spin { animation: ev-pulse 1.4s ease-in-out infinite; border-top-color: var(--line, #e3e3e8); }
  @keyframes ev-pulse { 50% { opacity: 0.35; } }
}

/* A report that was amended before it went out. Said on the row rather than
   only in the record: "what did we tell them" and "is this what the rebbe
   wrote" are different questions and this screen answers both. */
.rv-editedtag {
  margin-left: 8px; padding: 1px 7px; border-radius: 999px;
  background: var(--bg-off, #f5f5f7); color: var(--muted, #6b6b76);
  font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase;
}

/* The cycles list: one card shape, whatever the person does with it. */
.evr-card2 { display:flex; align-items:center; gap:16px; padding:1rem 1.25rem;
  background:var(--card,#fff); border:1px solid var(--line,#e3e3e8); border-radius:12px;
  margin-bottom:10px; }
.evr-card2-main { flex:1; min-width:0; }
.evr-card2-acts { display:flex; gap:8px; flex-shrink:0; }
.evr-title { margin:0 0 4px; font-size:1.02rem; font-weight:600; }
.evr-line { margin:4px 0 0; font-size:0.88rem; }
.evr-bar { height:5px; border-radius:3px; background:var(--bg-off,#f1f1f4); overflow:hidden; margin:8px 0 0; }
.evr-bar i { display:block; height:100%; background:var(--accent,#2a6ef0); }
a.btn.primary, .btn.primary { background:var(--accent,#2a6ef0); color:#fff; border-color:transparent; }

/* The review screen: a card per shiur, the table only when asked for.
   The bar is the shape of the shiur -- eleven excellent and one fair is
   visible before a word is read, and the one fair is what the table is for. */
.sh-card { display:flex; align-items:center; gap:16px; padding:1rem 1.25rem;
  background:var(--card,#fff); border:1px solid var(--line,#e3e3e8);
  border-radius:12px; margin-bottom:10px; }
.sh-main { flex:1; min-width:0; }
.sh-name { margin:0 0 8px; font-size:1.02rem; font-weight:600; }
.sh-bar { display:flex; height:9px; border-radius:5px; overflow:hidden; background:var(--bg-off,#f1f1f4); }
.sh-bar i { display:block; height:100%; }
.sh-bar i.rv-r1 { background:#2e7d4f; } .sh-bar i.rv-r2 { background:#6f9a2e; }
.sh-bar i.rv-r3 { background:#c79a10; } .sh-bar i.rv-r4 { background:#d4712c; }
.sh-bar i.rv-r5 { background:#c0392b; }
.sh-words { margin:7px 0 0; font-size:0.85rem; color:var(--muted,#6b6b76); }
.sh-acts { display:flex; flex-direction:column; gap:6px; flex-shrink:0; }
.sh-detail { margin:-4px 0 14px; padding:0 4px; }
@media (max-width:700px) {
  .evr-card2, .sh-card { flex-direction:column; align-items:stretch; }
  .evr-card2-acts, .sh-acts { flex-direction:row; }
}

/* A send result that failed is not a status line, it is the only record that
   a letter did not go -- it used to be erased by the repaint a moment later. */
.rv-sendresult.bad { border: 1px solid #e0a0a0; background: #fdf3f3; }
.rv-sendresult.bad p { margin: 8px 0 4px; font-size: 0.9rem; }
.rv-sendresult .btn.ghost { margin-top: 10px; }

/* The sent list: search and sortable headers. After a real round this is
   several hundred rows and the question is always about one of them. */
.sent-bar { display:flex; align-items:center; gap:12px; margin:0 0 10px; }
.sent-q { flex:1; max-width:340px; padding:7px 11px; font:inherit; font-size:0.9rem;
  border:1px solid var(--line,#e3e3e8); border-radius:7px; background:var(--card,#fff);
  color:inherit; }
.sent-q:focus { outline:2px solid var(--accent,#2a6ef0); outline-offset:-1px; }
th.sent-sort { cursor:pointer; user-select:none; white-space:nowrap; }
th.sent-sort:hover { text-decoration:underline; }
th.sent-sort.on { color:var(--accent,#2a6ef0); }
.sent-arrow { display:inline-block; width:0.9em; font-size:0.85em; }
@media (max-width:700px) { .sent-q { max-width:none; } }

/* A sent row that opens in place. The office reads this list to answer "what
   did we tell them and who got it", and a link that opened a PDF in a new tab
   lost their place in a list of two hundred and twenty-four. */
tr.sent-row { cursor: pointer; }
tr.sent-row:hover { background: var(--bg-off, #f6f6f8); }
tr.sent-row.on { background: var(--bg-off, #f1f1f4); }
.sent-caret { display: inline-block; width: 0.9em; color: var(--muted, #6b6b76); }
tr.sent-detail > td { background: var(--bg-off, #f7f7f9); padding: 0; }
.sd { padding: 14px 18px 16px 34px; display: grid; gap: 14px;
      grid-template-columns: minmax(240px, 1fr) minmax(220px, 1fr); align-items: start; }
.sd-env { display: grid; gap: 4px; }
.sd-line { display: flex; gap: 10px; font-size: 0.87rem; line-height: 1.45; }
.sd-k { flex: 0 0 5.5rem; color: var(--muted, #6b6b76); }
.sd-v { flex: 1; word-break: break-word; }
.sd-marks { border-collapse: collapse; font-size: 0.87rem; width: 100%; }
.sd-marks td { padding: 3px 10px 3px 0; border: 0; }
.sd-marks td:last-child { text-align: right; font-weight: 600; white-space: nowrap; }
.sd-note { grid-column: 1 / -1; margin: 0; font-size: 0.87rem; line-height: 1.5; }
@media (max-width: 760px) { .sd { grid-template-columns: 1fr; padding-left: 18px; } }

/* The way back from a mistake, at the foot of an opened sent row. */
.sd-act { grid-column: 1 / -1; display: flex; align-items: center; gap: 12px;
  flex-wrap: wrap; padding-top: 4px; }
.sd-act .muted { font-size: 0.82rem; }

/* The scan list: one row per page, the refused ones picked out of the middle. */
.k-list td { vertical-align: top; }
.k-sub { font-size: 0.78rem; color: var(--muted, #6b6b76); margin-top: 2px; }
tr.k-flag > td { background: #fff8e8; }
tr.k-why > td { background: #fff8e8; border-top: 0; padding-top: 0;
  font-size: 0.82rem; color: #8a6400; }
button.linkish { background: none; border: 0; padding: 0; font: inherit;
  color: var(--accent, #2a6ef0); cursor: pointer; text-decoration: underline; }
@media (prefers-color-scheme: dark) {
  tr.k-flag > td, tr.k-why > td { background: #3a3220; }
}

/* Staff: a rebbe's boys, opened under his row. */
.sd-boys { padding: 12px 18px 14px 34px; display: grid; gap: 4px 20px;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.sb { display: flex; justify-content: space-between; gap: 12px;
  font-size: 0.87rem; padding: 2px 0; }
.sb-owed { font-weight: 600; }
.sb-owed .muted { color: #b06a00; }

/* A report's history, under its envelope. */
.sd-hist { grid-column: 1 / -1; border-top: 1px solid var(--line, #e3e3e8);
  padding-top: 10px; margin-top: 2px; display: grid; gap: 3px; }
.sd-h { display: grid; grid-template-columns: 8.5rem 1fr auto; gap: 10px;
  font-size: 0.84rem; line-height: 1.45; }
.sd-warn { color: #8a3d00; }
p.sd-warn { background: #fff4e5; border: 1px solid #f0c890; border-radius: 7px;
  padding: 8px 11px; }
@media (max-width: 760px) { .sd-h { grid-template-columns: 1fr; gap: 0; } }
