/* Applicant-facing stylesheet — the public form pages and the document upload
 * portal, and nothing the staff console loads.
 *
 * WHY IT IS SEPARATE FROM components.css
 * --------------------------------------
 * The two surfaces have different jobs and different readers. The console is a
 * dense tool for somebody who uses it every day on a desktop; the public form is
 * a one-off task on a phone, in a hurry, often by somebody who will never see it
 * again. Rules written for one are wrong for the other — 36px toolbar buttons
 * against 44px tap targets, 13px table text against 16px inputs that iOS will
 * not zoom.
 *
 * Loading it is `{% block head_extra %}` on public_base.html, which puts it
 * AFTER components.css and BEFORE the compiled Tailwind. That order is
 * load-bearing: see the comment on the tailwind.built.css <link> in base.html.
 *
 * WHERE A RULE BELONGS, between the three files this surface reads:
 *   components.css               shared with the console, or shared between the
 *                                applicant's prose pages and its question sheet
 *                                (`.public-title`, `.milestone`, `.factline`,
 *                                `.steps`, `.packet`, `.answers`, `.actions`,
 *                                `.commit`).
 *   HERE                         applicant-only chrome and page furniture: the
 *                                gate, the review list, the receipt's reference
 *                                block, the sticky bar, the alert box.
 *   public/_public_css.html      the question sheet ONLY — because that partial
 *                                is also rendered where no <head> of ours exists
 *                                (the builder's preview), so its styles have to
 *                                travel inline with it.
 *
 * Every value is a token from components.css. This surface invents no colours.
 */

/* The applicant surface never shows the console's staggered entrance: the page
   is one column of questions and animating it in delays the first tap on a slow
   phone for no gain. */
.public-shell .stagger > * { opacity: 1; animation: none; }

/* ─── Chrome ──────────────────────────────────────────────────────────────── */

.public-shell { display: flex; flex-direction: column; min-height: 100vh; }
.public-shell > main { flex: 1 0 auto; width: 100%; }

/* The reading column, stated here as well as in the utility classes on the
   markup. components.css's own doctrine is that this vocabulary keeps working
   whether or not `build-css.sh forms` has been run, and the applicant surface is
   the one where the unstyled fallback is not "slightly plain" — it is a 1000px
   line of inputs on a phone, which is the exact failure this measure exists to
   prevent. The values are the same ones `max-w-form`, `px-5` and `py-8` compile
   to, so the two agree rather than fight. */
.public-masthead > div,
.public-shell > main,
.public-foot {
  margin-inline: auto;
  max-width: 42rem;
  width: 100%;
}
.public-masthead > div {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
}
.public-shell > main { padding: 2rem 1.25rem; }
.public-shell > main > * + * { margin-top: 1.5rem; }

.public-foot {
  padding: 1.25rem 1.25rem 2.5rem;
  border-top: 1px solid var(--border);
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--ink-faint);
}
/* A second, quieter line under a card — the "staff sign in elsewhere" aside on
   the gate. Not the footer, because it is about this page rather than the app. */
.public-foot__aside {
  margin-top: 1.75rem;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--ink-faint);
}

/* The second-level heading on a prose page. Sized between `.public-title` and
   body text, and given the rule that says a new part of the page starts here —
   the receipt has three of these and without the rule they read as paragraphs
   in bold. */
.public-subhead {
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  font-family: var(--font-heading);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink-heading);
}

/* ─── Errors ──────────────────────────────────────────────────────────────────
   ONE error box for the whole applicant surface: the section sheet's summary, the
   gate's refusal, the review page's whole-form failure, and the "nothing was
   submitted" notice on a form that closed mid-flow.

   Words first — the tint is the second signal, never the only one (WCAG 1.4.1) —
   and every entry that names a question is a LINK to it, because reaching the
   fourteenth question on a long page by scrolling for a red border is not a
   recovery path. #6E1512 on --danger-soft is 9.4:1. */
.alert {
  padding: 0.9375rem 1.125rem;
  border: 1px solid #EFCBC9;
  border-left: 3px solid var(--danger);
  border-radius: var(--r-lg);
  background: var(--danger-soft);
}
.alert__title {
  font-family: var(--font-heading);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.45;
  color: #6E1512;
}
.alert__list { margin-top: 0.375rem; display: flex; flex-direction: column; }
/* Each entry is a target a thumb has to hit: 44px of row, not a 19px line of
   underlined text. */
.alert__list a {
  display: flex;
  align-items: center;
  padding: 0.4375rem 0;
  min-height: 2.75rem;
  font-size: 0.875rem;
  line-height: 1.45;
  color: #6E1512;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.alert__list span {
  padding: 0.4375rem 0;
  font-size: 0.875rem;
  line-height: 1.45;
  color: #6E1512;
}
.alert--group { margin: 0.25rem 0 1rem; }
/* The pay page on a form charging in Square's sandbox: not an error, so the
   warning amber rather than the refusal red. #6B3F00 on --warning-soft is the
   pairing `.notice--warning` already uses in the console. */
.alert--test {
  margin: 1rem 0;
  border-color: #F0D5A4;
  border-left-color: var(--warning);
  background: var(--warning-soft);
}
.alert--test .alert__title,
.alert--test .alert__list span { color: #6B3F00; }

/* ─── The password gate ───────────────────────────────────────────────────────
   One card, one field, one button, and nothing that describes the password. */
.gate { max-width: 26rem; }
.gate__form { margin-top: 1.5rem; }
.gate__label {
  display: block;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 0.5rem;
}
/* The reveal sits INSIDE the field's box rather than beside it: at 320px a
   button next to a full-width input leaves the input too narrow to show what was
   typed, which is the one thing the button exists to help with. */
.gate__control { position: relative; }
.gate__control .field-input {
  font-size: 1rem;
  min-height: 2.75rem;
  padding-right: 5rem;
  background: var(--surface);
}
.gate__reveal {
  position: absolute;
  top: 50%;
  right: 0.375rem;
  transform: translateY(-50%);
  min-height: 2.25rem;
  padding: 0 0.625rem;
  border: none;
  border-radius: var(--r-sm);
  background: transparent;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--brand);
  cursor: pointer;
}
.gate__reveal:hover { color: var(--brand-hover); text-decoration: underline; }
.gate__reveal:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.gate__submit { width: 100%; justify-content: center; margin-top: 1rem; }
.gate .alert { margin-top: 1.25rem; }

/* ─── The review list ─────────────────────────────────────────────────────────
   One group per section, with ONE Edit link at its head. A link beside every
   answer is thirty links, thirty ways to lose your place, and thirty round trips
   to change two things on the same page. */
.review-group {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}
.review-group__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.review-group__title {
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink-heading);
}
.review-group__edit {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  flex: none;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--brand);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.review-group__edit:hover { color: var(--brand-hover); }

/* An answer the applicant left blank still gets a row, because the gap is the
   thing they are checking for. `format_answer` renders it as an em dash. */
.answers dd:empty::before { content: "—"; color: var(--ink-faint); }

/* ─── The documents list ──────────────────────────────────────────────────────
   Shown twice: on the review page as "what you will be asked for next", and on
   the receipt as "what is still outstanding". Each row says Required or Optional
   in words, because that distinction decides whether somebody goes looking for a
   transcript this week or next month. */
.packet-list { margin-top: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem; }
.packet-list > li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.25rem 0.75rem;
  padding: 0.75rem 0.875rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--sky);
  border-radius: var(--r-md);
  background: var(--surface);
}
.packet-list__label { font-size: 0.9375rem; line-height: 1.4; color: var(--ink); }
.packet-list__flag {
  flex: none;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ─── The submit form ─────────────────────────────────────────────────────── */

.commit-form { margin-top: 2rem; }

/* The Turnstile slot sits directly above the button with a line explaining why it
   is there — an unexplained challenge on a college application reads as a fault.
   The widget is 300px wide at its narrowest, so the box scrolls rather than
   pushing the page sideways on a 320px phone. */
.captcha {
  margin-top: 1.25rem;
  padding: 1rem 1.125rem;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
}
.captcha__note { font-size: 0.875rem; line-height: 1.5; color: var(--ink-soft); }
.captcha .cf-turnstile { margin-top: 0.75rem; overflow-x: auto; }

/* The honeypot. Positioned off-screen rather than `display: none` — some bots
   skip hidden fields and none of them skip one that is merely somewhere else —
   and unreachable by keyboard or screen reader (`tabindex="-1"`, `aria-hidden`
   on the wrapper), so nobody who should never see it ever can. */
.trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ─── The receipt's reference number ──────────────────────────────────────────
   The one thing on the page the applicant will need again — quoted on the phone,
   pasted into an email, searched for by the department. So it is a printed
   record rather than a sentence with a code in it: mono, tabular, large, on its
   own line, and `user-select: all` so a single tap or click takes the whole
   string and not half of it. */
.receipt__reference-label {
  margin-top: 1.125rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.receipt__reference {
  margin-top: 0.25rem;
  font-size: clamp(1.5rem, 1.1rem + 2vw, 1.875rem);
  font-weight: 500;
  line-height: 1.15;
  color: var(--navy);
  user-select: all;
  -webkit-user-select: all;
  overflow-wrap: anywhere;
}

/* ─── The sticky action bar ───────────────────────────────────────────────────
   On a phone the forward button leaves the viewport the moment a section has more
   than four questions on it, and a button you have to scroll to find is a button
   people assume is not there. Below 640px the flow's ONE action row pins itself
   to the bottom edge, bleeding to the full page width (the reading column's
   1.25rem padding is cancelled) and padded past the home indicator.

   Scoped to the two forms that own an action row, not to `.actions` globally:
   `public_base.html` has an `action_bar` block precisely so there is never a
   second one to fight with this. */
@media (max-width: 639px) {
  #apply-section .actions,
  .commit-form .actions {
    position: sticky;
    bottom: 0;
    z-index: 5;
    margin-inline: -1.25rem;
    padding: 0.75rem 1.25rem;
    padding-bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
    background: var(--bg);
    border-top: 1px solid var(--border);
    box-shadow: 0 -1px 12px rgba(1, 33, 75, 0.06);
  }
  #apply-section .actions .btn,
  .commit-form .actions .btn { flex: 1 1 auto; justify-content: center; }
  #apply-section .actions .actions__back,
  .commit-form .actions .actions__back { flex: none; }

  .public-foot { padding-inline: 1.25rem; }
  .review-group__head { align-items: flex-start; }
}

@media (prefers-reduced-motion: reduce) {
  .gate__reveal, .review-group__edit { transition: none; }
}

/* ─── Payments ────────────────────────────────────────────────────────────────
   The pay page, and the receipt block the done page grows when a fee was
   charged. Every value is a token from components.css; the itemised quote
   itself (`.quote`) is shared vocabulary and lives there, beside `.milestone`,
   because it is drawn inside that band.

   The pay step is the ONE screen in this flow that requires JavaScript, so the
   two states it can be in that no other page has — "the card field has not
   loaded yet" and "Square refused that card" — both need somewhere to be said.
   `.pay-error` is that place: it is always in the DOM (an aria-live region
   inserted on first error is a region a screen reader may never announce),
   empty until there is something to say, and it takes no space while it is. */
.pay-error:empty { display: none; }
.pay-error {
  margin-top: 1.25rem;
  padding: 0.9375rem 1.125rem;
  border: 1px solid #EFCBC9;
  border-left: 3px solid var(--danger);
  border-radius: var(--r-lg);
  background: var(--danger-soft);
}
.pay-error .alert__title {
  font-family: var(--font-heading);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.45;
  color: #6E1512;
}
.pay-error:focus-visible { outline: 2px solid var(--danger); outline-offset: 2px; }

/* The "we could not confirm your payment" page leads with the warning and names
   itself second, so the heading would otherwise sit directly on the alert's
   border. It is the only page in the flow that opens this way. */
.alert + .public-title { margin-top: 1.5rem; }

/* A second line inside the refusal box, for the sentence about the refund. Set
   in the same ink as the title but at reading weight: it is the explanation, not
   the headline, and it must not be mistaken for a second failure. */
.alert__note {
  margin-top: 0.4375rem;
  font-size: 0.875rem;
  line-height: 1.55;
  color: #6E1512;
}

/* Square draws its own iframes inside this container. We give it height so the
   page does not jump when they attach, and nothing else: styling the inside of
   a PCI iframe is not ours to do. */
#card-container { min-height: 5.5rem; }

.pay-secured {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  margin-top: 0.625rem;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--ink-faint);
}
.pay-secured svg { width: 0.75rem; height: 0.75rem; flex: none; }

/* The reason the one button on the page is off. Quiet, directly under it, and
   removed by `pay.js` the moment the card field attaches — a hint that outlives
   its condition is worse than no hint. */
.pay-ready {
  margin-top: 0.625rem;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink-faint);
  text-align: center;
}

/* The button on this page is the action, and there is no second control beside
   it, so it takes the full width rather than sitting in a row with a gap where
   a "back" button would be. Back is still there, as a link, underneath. */
.actions--single { flex-direction: column; align-items: stretch; }
.actions--single .btn { width: 100%; justify-content: center; }
.actions--single .actions__back { justify-content: center; }

/* The sentence that lets somebody press the button. Quiet, under the action,
   and stated once — it is a promise about what happens next, not a warning. */
.promise {
  margin-top: 1rem;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--ink-faint);
  text-align: center;
}
.promise strong { color: var(--ink-soft); font-weight: 500; }

/* The receipt page's money block. Same shape as the documents strip — a ruled
   card with the record value set in mono — because on that page it is one more
   fact about the application, not a second milestone competing with the
   reference number. */
.paid {
  margin-top: 1.25rem;
  padding: 0.9375rem 1.125rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--success);
  border-radius: var(--r-lg);
  background: var(--surface);
}
.paid__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.paid__label {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.paid__amount {
  font-size: 1.125rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.paid__card { margin-top: 0.25rem; font-size: 0.8125rem; color: var(--ink-soft); }
.paid__link {
  margin-top: 0.5rem;
  display: flex;
  align-items: center;
  min-height: 2.75rem;
  font-size: 0.875rem;
}
.paid__link a {
  color: var(--brand);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.paid__note { margin-top: 0.375rem; font-size: 0.8125rem; line-height: 1.5; color: var(--ink-soft); }

/* ─── The applicant's dashboard (2026-10-24) ──────────────────────────────────
   The hub (`upload/dashboard.html`), the documents page and the checklist page
   behind it — approved board: rendition A "Hub". Every class is prefixed
   (`dash-`, `task-`, `todo-`) because the board's own names (`.card`, `.pill`,
   `.btn`, `.flash`) already mean something else in components.css, and this
   file loads after it.

   The three pages carry `.dash` on their content root. On a wide screen that
   widens the whole shell (masthead, column, footer together, so their left
   edges stay aligned) to the board's 1000px laptop frame; the documents and
   checklist pages then keep a 720px reading column inside it (`.dash-narrow`).
   `:has()` keeps public_base.html untouched; a browser without it keeps the
   42rem column, which is the phone layout and works everywhere.

   Two tints are the board's and have no token of their own: the done card's
   ground #F3FAF7 and its border #BFE3D6 (between --success-soft and white), and
   the all-set ink #0B5E49 (--success one step darker, 7.1:1 on --success-soft). */

.public-shell:has(.dash) > main { padding: 1.375rem 1.25rem 1.75rem; }
.public-shell > main > .dash { line-height: 1.55; }

@media (min-width: 45rem) {
  .public-shell:has(.dash) .public-masthead > div,
  .public-shell:has(.dash) > main,
  .public-shell:has(.dash) .public-foot {
    max-width: 62.5rem;
    padding-inline: 2.5rem;
  }
  .public-shell:has(.dash) > main { padding-top: 2.25rem; padding-bottom: 3rem; }
}

.dash { display: flex; flex-direction: column; gap: 1.125rem; }
.dash-narrow { display: flex; flex-direction: column; gap: 1.125rem; width: 100%; max-width: 45rem; margin-inline: auto; }

.dash-ico {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex: none;
}
.dash-ico--lg { width: 22px; height: 22px; }
.dash-ico--check { width: 15px; height: 15px; stroke-width: 2.6; }
.dash-ico--quiet { color: var(--ink-soft); }

/* The hub's head: the reference as a printed record, then the greeting. */
.dash-ref { margin: 0; font-family: var(--font-mono); font-size: 0.8125rem; color: var(--ink-soft); }
.dash-hello {
  margin: 0.25rem 0 0;
  font-family: var(--font-heading);
  font-size: 1.875rem;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--navy);
  overflow-wrap: anywhere;
}
.dash-lede { margin: 0.5rem 0 0; font-size: 1rem; color: var(--ink); }
.dash-h1 {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.015em;
  color: var(--navy);
}
@media (min-width: 45rem) { .dash-hello { font-size: 2.375rem; } }

/* "Nothing else is needed from you": the one green panel, shown once all set. */
.dash-allset {
  display: flex;
  gap: 0.875rem;
  align-items: flex-start;
  padding: 1.125rem;
  border-radius: 14px;
  background: var(--success-soft);
  color: #0B5E49;
}
.dash-allset__title { margin: 0 0 0.25rem; font-size: 1.1875rem; color: #0B5E49; }
.dash-allset p { margin: 0; font-size: 0.9375rem; }
/* Review R1: the applicant's part is done, a visible signature is still on its way. */
.dash-allset--waiting { background: #EAF2FE; color: #01214B; }
.dash-allset--waiting .dash-allset__title { color: #01214B; }

/* The rail: one segment per REQUIRED task, grouped Documents | Prerequisites | Checklist with a
   wider gap between the groups. Each group's flex-grow is its segment count
   (set inline), so every segment on the rail is the same width. */
.dash-rail-box {
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
}
.dash-rail-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  margin-bottom: 0.625rem;
}
.dash-count { font-family: var(--font-heading); font-size: 1.25rem; font-weight: 600; color: var(--navy); }
.dash-count-sub { font-size: 0.875rem; color: var(--ink-soft); }
.dash-rail { display: flex; gap: 0.75rem; }
/* The min-width keeps a one-task group's legend ("Documents 0 of 1") on one
   line beside a long checklist; rail and legend share it, so they stay aligned. */
.dash-rail-group { display: flex; gap: 4px; min-width: 7.25rem; }
.dash-seg { flex: 1 1 0; height: 12px; border-radius: 4px; background: var(--brand-soft); }
.dash-seg--on { background: var(--brand); }
.dash-seg--all { background: var(--success); }
.dash-legend { display: flex; gap: 0.75rem; margin-top: 0.5rem; font-size: 0.8125rem; color: var(--ink-soft); }
.dash-legend span { min-width: 7.25rem; white-space: nowrap; }
.dash-mini { display: flex; gap: 4px; }
.dash-mini .dash-seg { height: 6px; }

/* One action card per task type; side by side once there is room. */
.dash-cards { display: flex; flex-direction: column; gap: 0.875rem; }
@media (min-width: 45rem) {
  .dash-cards--2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem; }
}
.dash-card {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1.125rem;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
}
.dash-card--done { background: #F3FAF7; border-color: #BFE3D6; }
.dash-card__head { display: flex; gap: 0.875rem; align-items: flex-start; }
.dash-card__text { flex: 1 1 auto; min-width: 0; }
.dash-tile {
  display: grid;
  place-items: center;
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--brand-tint);
  color: var(--brand);
}
.dash-tile--done { background: var(--success); color: var(--on-success); }
.dash-card__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.1875rem;
  font-weight: 600;
  line-height: 1.25;
  color: var(--navy);
}
.dash-card__status { margin: 2px 0 0; font-size: 0.9375rem; color: var(--ink); }
.dash-card__next { margin: 0; font-size: 0.875rem; color: var(--ink-soft); }
.dash-card__next b { font-weight: 600; color: var(--ink); }
/* The card's button is the last thing in it, so on the two-column grid the two
   buttons line up along the bottom however long the status lines are. */
.dash-card > .dash-btn { margin-top: auto; }

/* The board's full-width 48px button — a link on the hub, a submit elsewhere. */
.dash-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  min-height: 48px;
  padding: 0.75rem 1rem;
  border: 1px solid var(--brand);
  border-radius: 10px;
  background: var(--brand);
  color: var(--on-brand);
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.25;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}
.dash-btn:hover { background: var(--navy); border-color: var(--navy); color: var(--on-brand); }
.dash-btn--quiet { background: var(--surface); color: var(--navy); border-color: var(--border-strong); }
.dash-btn--quiet:hover { background: var(--brand-tint); color: var(--navy); border-color: var(--brand); }
.dash-btn:focus-visible,
.dash-back:focus-visible,
.task-textbtn:focus-visible,
.task-res__row:focus-visible,
.task-more > summary:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* What the applicant would otherwise have to remember: when they applied, and
   how to come back. Never when the link stops working (the owner's rule). */
.dash-about {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.375rem 1rem;
  margin: 0;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
  font-size: 0.875rem;
}
.dash-about dt { color: var(--ink-soft); }
.dash-about dd { margin: 0; color: var(--ink); }

/* ── The two task pages ── */
.dash-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  align-self: flex-start;
  min-height: 44px;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--brand);
  text-decoration: none;
}
.dash-back:hover { color: var(--navy); text-decoration: underline; }
.dash-note {
  margin: 0;
  padding: 0.75rem 0.875rem;
  border-radius: 10px;
  background: var(--brand-tint);
  font-size: 0.9375rem;
  color: var(--navy);
}
.dash-strip { display: flex; flex-direction: column; gap: 0.5rem; }
.dash-strip__row { display: flex; justify-content: space-between; gap: 0.75rem; font-size: 0.875rem; }
.dash-strip__count { font-family: var(--font-mono); font-weight: 500; color: var(--navy); }
.dash-strip__left { color: var(--ink-soft); }
.dash-strip__track { display: flex; gap: 4px; }
.dash-strip__track .dash-seg { height: 8px; }

/* One task: a document to send or a checklist item to do. */
.task-list { display: flex; flex-direction: column; gap: 0.875rem; margin: 0; padding: 0; list-style: none; }
.task {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  /* The 303 after a tick lands on `#item-<id>`; keep the title off the edge. */
  scroll-margin-top: 1rem;
}
.task--done { border-color: #BFE3D6; }
.task__head { display: flex; gap: 0.75rem; align-items: flex-start; }
.task__heading { flex: 1 1 auto; min-width: 0; }
/* A mark only on a finished task (owner, 2026-10-30): the empty ring an unfinished
   one wore read as a radio button. With no mark the title starts at the card's
   edge, in line with what is under it. A not-needed prerequisite is not finished,
   so its skip glyph stays hidden too (`display: grid; place-items: center` on
   `.task__glyph.task__glyph--skip` brings it back). */
.task__glyph {
  display: none;
  flex: none;
  width: 26px;
  height: 26px;
  margin-top: 1px;
  border-radius: 50%;
}
.task__glyph--done { display: grid; place-items: center; background: var(--success); color: var(--on-success); }
.task__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--navy);
  overflow-wrap: anywhere;
}
/* The state is a WORD; the tint is the second signal (WCAG 1.4.1). */
.task__pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 6px;
  padding: 1px 9px;
  border-radius: var(--r-full);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 20px;
  white-space: nowrap;
}
.task__pill--req { background: var(--warning-soft); color: var(--warning-ink); }
.task__pill--opt { border: 1px solid var(--border); background: var(--surface); color: var(--ink-soft); font-weight: 500; }
.task__pill--done { background: var(--success-soft); color: var(--success); }
.task__pill--off { background: var(--surface-2); color: var(--ink-soft); }
/* Asked once approved (2026-11-05, board "Applicant"): green like the Approved card. */
.task__pill--after { border: 1px solid #9DD3BF; background: #E9F6F1; color: #0B5E49; }
.task__asked { margin: 0; font-size: 0.8125rem; font-weight: 500; color: #0B5E49; }
.task__desc { margin: 0; font-size: 0.9375rem; color: var(--ink); }
.task__desc p + p, .task__desc ul, .task__desc ol { margin-top: 0.5rem; }
.task__desc ul { list-style: disc; padding-left: 1.25rem; }
.task__desc ol { list-style: decimal; padding-left: 1.25rem; }
.task__desc a { color: var(--brand); text-decoration: underline; text-underline-offset: 2px; }
.task__hint { margin: -4px 0 0; font-size: 0.8125rem; color: var(--ink-soft); }
.task__form { display: flex; flex-direction: column; gap: 0.75rem; margin: 0; }
.task__label { display: block; margin-bottom: 4px; font-size: 0.875rem; font-weight: 600; color: var(--ink); }
.public-shell .task .field-input { min-height: 46px; font-size: 1rem; background: var(--surface); }
.task .captcha { margin-top: 0; }
.task-error { margin: 0; font-size: 0.875rem; color: var(--danger-ink); }

/* Links and files an item offers: a row each, 52px tall, the whole row a target. */
.task-res { display: flex; flex-direction: column; gap: 0.5rem; }
.task-res__row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 52px;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
}
.task-res__row:hover { border-color: var(--brand); background: var(--brand-tint); color: var(--ink); }
.task-res__ico { display: grid; place-items: center; flex: none; width: 32px; height: 32px; border-radius: var(--r-md); }
.task-res__ico--link { background: var(--brand-tint); color: var(--brand); }
.task-res__ico--pdf { background: var(--danger-soft); color: var(--danger-ink); font-family: var(--font-sans); font-size: 10px; font-weight: 700; }
.task-res__main { flex: 1 1 auto; min-width: 0; }
.task-res__name { display: block; font-size: 0.9375rem; font-weight: 600; color: var(--navy); overflow-wrap: anywhere; }
.task-res__host { display: block; font-family: var(--font-mono); font-size: 0.75rem; color: var(--ink-soft); overflow-wrap: anywhere; }

/* A document on file: the file, and a quiet way to replace it. The replace form
   is a <details> so the page needs no script: closed, its summary sits at the
   row's right edge like the board's "Replace"; open, it takes the full width
   under the file. */
.task-file {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  padding: 0.625rem 0.75rem;
  border-radius: 10px;
  background: var(--bg);
}
.task-file > .task-res__main { flex: 1 1 0; }
.task-file > .task-replace { margin-left: auto; }
.task-file > .task-replace[open] { flex-basis: 100%; margin-left: 0; }
.task-replace > summary { list-style: none; }
.task-replace > summary::-webkit-details-marker { display: none; }
.task-replace[open] > summary { padding-left: 0; }
.task-replace .task__form { margin-top: 0.25rem; }
.task-sent { margin: 0; padding: 0.625rem 0.75rem; border-radius: 10px; background: var(--bg); font-size: 0.9375rem; }

/* ── A multi-signer e-sign agreement (2026-10-26) ──
   "Sent to" with how many were sent, one row per person it went to (a pill per
   row only when staff show the status), and the add form folded into "Add
   another signer". No cap is ever shown (revision 1). Rendition "Forms
   multi-signer agreements". */
.task__pill--sent { background: var(--brand-tint); color: var(--navy); }
.task__pill--progress { background: var(--warning-soft); color: var(--warning-ink); }
.task__pill--cancelled { border: 1px solid var(--border); background: var(--surface); color: var(--ink-soft); }
.signers { display: flex; flex-direction: column; gap: 0.5rem; }
.signers__head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.75rem; font-size: 0.875rem; }
.signers__title { font-weight: 600; color: var(--navy); }
.signers__count { font-family: var(--font-mono); color: var(--navy); }
.signers__list { margin: 4px 0 0; padding: 0; list-style: none; border: 1px solid var(--divider); border-radius: 12px; }
.signer { display: flex; flex-direction: column; gap: 4px; padding: 0.75rem; border-bottom: 1px solid var(--divider); }
.signer:last-child { border-bottom: 0; }
.signer--off { background: #F9FAFB; }
.signer__top { display: flex; justify-content: space-between; align-items: flex-start; gap: 0.625rem; }
.signer__top + .signer__who { margin-top: -2px; }
.signer__who { display: flex; flex-direction: column; min-width: 0; }
.signer__name { font-size: 0.9375rem; font-weight: 600; color: var(--navy); overflow-wrap: anywhere; }
.signer--off .signer__name { color: var(--ink-soft); text-decoration: line-through; }
.signer__mail { font-family: var(--font-mono); font-size: 0.75rem; color: var(--ink-soft); overflow-wrap: anywhere; }
.signer__when { font-size: 0.8125rem; color: var(--ink-soft); }
.signer__again { align-self: flex-start; padding: 0 0.375rem; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--navy); font-size: 0.75rem; font-weight: 500; line-height: 1.125rem; }
.signer__resend { display: inline-flex; align-items: center; gap: 0.375rem; min-height: 44px; font-size: 0.875rem; font-weight: 600; color: var(--brand); text-decoration: none; }
.signer__resend:hover { color: var(--navy); }
.signer__resend:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.signers__refill { margin: 0; font-size: 0.875rem; color: var(--navy); }
.signers__notice { margin: 0; padding: 0.625rem 0.75rem; border-radius: 10px; background: var(--brand-tint); color: var(--navy); font-size: 0.875rem; }
.signers__notice:focus { outline: none; }
.signers__notice:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.signer .task__pill { flex: none; margin-top: 0; }
.signer .task__hint { margin: 0; }
.signer__copy { min-height: 44px; margin-top: 2px; }
.signers__add { padding-top: 0.75rem; border-top: 1px solid var(--divider); }
.signers__add > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  font-weight: 600;
  color: var(--brand);
  list-style: none;
  cursor: pointer;
}
.signers__add > summary::-webkit-details-marker { display: none; }
.signers__add > summary:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.signers__add[open] > summary { color: var(--navy); }
.signers__add .task__form { margin-top: 0.5rem; }
.signers__full { margin: 0; padding: 0.75rem; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); font-size: 0.875rem; }

/* ── A multi-file upload item (2026-10-29) ──
   "Your files" reuses the signer list's frame (`.signers__head`, `.signers__list`)
   with one row per file; Remove asks in place, and the item finishes with "That's
   all my files". The limit is never shown. Rendition "Forms multi-file documents",
   boards "Applicant · Your documents" and "Applicant · the card in each state".
   An open Remove keeps its summary at the row's right edge (absolute) while the
   question takes the full width under the file. */
.upfile { position: relative; padding: 0.625rem 0.75rem; border-bottom: 1px solid var(--divider); }
.upfile:last-child { border-bottom: 0; }
.upfile__row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; min-height: 44px; }
.upfile__row > .task-res__main { flex: 1 1 0; }
.upfile__office {
  display: inline-block;
  margin-top: 2px;
  padding: 0 7px;
  border-radius: var(--r-full);
  background: var(--surface-2);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ink-soft);
}
.upfile__remove { flex: none; }
.upfile__remove > summary { list-style: none; }
.upfile__remove > summary::-webkit-details-marker { display: none; }
.upfile__remove[open] { flex-basis: 100%; }
.upfile__remove[open] > summary { position: absolute; top: 0.625rem; right: 0.75rem; }
.upfile:has(.upfile__remove[open]) .task-res__main { padding-right: 4.5rem; }
.upfile__confirm {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.75rem;
  border-radius: 10px;
  background: var(--danger-soft);
}
.upfile__confirm p { margin: 0; font-size: 0.875rem; color: var(--ink); overflow-wrap: anywhere; }
.upfile__confirm-row { display: flex; align-items: center; gap: 0.75rem; }
.upfile__confirm form { margin: 0; }
.dash-btn--danger {
  width: auto;
  min-height: 44px;
  padding: 0.5rem 1rem;
  font-size: 0.9375rem;
  background: var(--danger);
  border-color: var(--danger);
}
.dash-btn--danger:hover { background: var(--danger-ink); border-color: var(--danger-ink); }
.files-finish {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  padding: 0.875rem;
  border: 1px solid #B9D3FA;
  border-radius: 12px;
  background: var(--brand-tint);
}
.files-finish__q { margin: 0; font-size: 0.9375rem; color: var(--ink); }
.files-finish__q b { display: block; font-family: var(--font-heading); font-size: 1rem; color: var(--navy); }
.files-finish form { margin: 0; }

.task-textbtn {
  min-height: 44px;
  padding: 8px 4px;
  border: 0;
  background: none;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--brand);
  cursor: pointer;
}
.task-textbtn:hover { color: var(--navy); text-decoration: underline; }

/* A ticked item: when, and the way back. */
.task-done-line {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 4px 4px 4px 12px;
  border-radius: 10px;
  background: var(--success-soft);
  font-size: 0.875rem;
  color: #0B5E49;
}
.task-done-line form { margin: 0; flex: none; }

/* A ticked item folds its notes, links and files away. */
.task-more { display: flex; flex-direction: column; }
.task-more > summary {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  padding-left: 0;
  list-style: none;
}
.task-more > summary::-webkit-details-marker { display: none; }
.task-more__body { display: flex; flex-direction: column; gap: 0.75rem; margin-top: 0.25rem; }
.task-more[open] .task-more__show,
.task-more:not([open]) .task-more__hide { display: none; }

/* ─── The receipt's "Still to do" ─────────────────────────────────────────── */
.todo-group { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.875rem; }
.todo-group__label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--ink);
}
.todo-list { display: flex; flex-direction: column; gap: 0.5rem; margin: 0; padding: 0; list-style: none; }
.todo-task {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  font-size: 0.9375rem;
  color: var(--ink);
}
.todo-task__flag {
  flex: none;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.todo-note { margin: 0.5rem 0 0; font-size: 0.875rem; color: var(--ink-soft); }

/* ─── Prerequisites (2026-10-24) ──────────────────────────────────────────────
   The hub's third card and three-group rail, and the grades page behind it
   (`upload/prerequisites.html`, `_prereq_card.html`) — approved board
   "Applicant". The grades page reuses the `task` card; what it adds is prefixed
   `prq-`. The GPA readout is the page's one bold element: the applicant is shown
   a figure beside a minimum, never a verdict word (plan D2). Since 2026-10-30 (the
   owner) it carries the staff card's threshold colours, below. */

/* Three groups on the rail: each group's name over its count, and a narrower
   floor per group, so Documents | Prerequisites | Checklist fit inside a 390px
   phone. 80px rather than the board's 76: "Prerequisites" itself is 79px, and a
   one-course group would otherwise push its name into the gap. One or two
   groups keep the one-line legend above. */
.dash-rail-box--3 .dash-rail-group { min-width: 5rem; }
.dash-rail-box--3 .dash-legend { font-size: 0.78125rem; line-height: 1.3; }
.dash-rail-box--3 .dash-legend span { display: flex; flex-direction: column; min-width: 5rem; }
.dash-rail-box--3 .dash-legend b { font-weight: 500; color: var(--ink); }

/* Three cards side by side from laptop width (64rem), where each card's GPA line
   ("Your GPA so far 3.43 · minimum 2.75") still fits on one line — below that
   they stack, as on a phone. */
@media (min-width: 64rem) {
  .dash-cards--3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.25rem; }
}

.dash-card__spacer { flex: 1 1 auto; }

/* The Prerequisites card's GPA line, ruled off from the "Next" line above it. */
.dash-card__gpa {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 0.375rem;
  margin: 0;
  padding-top: 0.625rem;
  border-top: 1px solid var(--divider);
  font-size: 0.875rem;
  color: var(--ink-soft);
}
.dash-card__gpa b { font-family: var(--font-heading); font-size: 1.125rem; font-weight: 600; color: var(--navy); }

/* The threshold colours (2026-10-30). Below is the staff card's own `.sd-gpa--below`
   (#FFF7F5 / #F3D2CC / #B0322A); staff have no green figure, so meets takes the done
   card's tint and --success, the ink of the staff "Meets" pill. On the hub the line
   becomes a band whose margin, border and padding cancel out (-8 + 1 + 7px), so the
   text keeps the width and left edge of the plain line and wraps exactly where it
   did. On a done card, already #F3FAF7, the band steps to --success-soft or it
   would not show. */
.dash-card__gpa--meets,
.dash-card__gpa--below {
  margin-inline: -0.5rem;
  padding: 0.375rem 0.4375rem;
  border: 1px solid;
  border-radius: 10px;
}
.dash-card__gpa--meets { background: #F3FAF7; border-color: #BFE3D6; }
.dash-card__gpa--meets b { color: var(--success); }
.dash-card--done .dash-card__gpa--meets { background: var(--success-soft); }
.dash-card__gpa--below { background: #FFF7F5; border-color: #F3D2CC; }
.dash-card__gpa--below b { color: #B0322A; }

/* The GPA box: the figure, a thin rule, the program's minimum. */
.prq-gpa {
  padding: 1.125rem 1.125rem 0.875rem;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
}
.prq-gpa__pair { display: flex; align-items: stretch; gap: 1.125rem; }
.prq-gpa__you { flex: 1 1 auto; }
.prq-gpa__num {
  display: block;
  font-family: var(--font-heading);
  font-size: 2.875rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--navy);
}
/* "—" before anything counts: a placeholder, not a figure. */
.prq-gpa__num--empty { color: var(--border-field); }
.prq-gpa__label { display: block; margin-top: 6px; font-size: 0.875rem; color: var(--ink); }
.prq-gpa__rule { flex: none; width: 1px; background: var(--border); }
/* The threshold colours: see `.dash-card__gpa--meets` above. */
.prq-gpa--meets { background: #F3FAF7; border-color: #BFE3D6; }
.prq-gpa--meets .prq-gpa__num { color: var(--success); }
.prq-gpa--meets .prq-gpa__rule { background: #BFE3D6; }
.prq-gpa--below { background: #FFF7F5; border-color: #F3D2CC; }
.prq-gpa--below .prq-gpa__num { color: #B0322A; }
.prq-gpa--below .prq-gpa__rule { background: #F3D2CC; }
.prq-gpa__min { display: flex; flex: 0 0 auto; flex-direction: column; justify-content: flex-end; }
.prq-gpa__min-num {
  font-family: var(--font-heading);
  font-size: 1.625rem;
  font-weight: 600;
  line-height: 1;
  color: var(--ink);
}
.prq-gpa__basis { margin: 0.75rem 0 0; font-size: 0.875rem; color: var(--ink-soft); }
.prq-gpa__how { margin-top: 0.5rem; font-size: 0.875rem; }
.prq-gpa__how > summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  font-weight: 600;
  color: var(--brand);
  list-style: none;
  cursor: pointer;
}
.prq-gpa__how > summary::-webkit-details-marker { display: none; }
.prq-gpa__how > summary:hover { color: var(--navy); text-decoration: underline; }
.prq-gpa__how > summary:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.prq-gpa__how p { margin: 0 0 0.375rem; color: var(--ink); }

/* One course: its code as a printed record above the name, then the pills. */
.prq-form { display: flex; flex-direction: column; gap: 1.125rem; margin: 0; }
.prq-code {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: 400;
  line-height: 1.3;
  color: var(--ink-soft);
}
.prq-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 6px; }
.prq-meta .task__pill { margin-top: 0; }
.task__pill--info { background: var(--bg); color: var(--ink); font-weight: 500; }
/* A refused course: the border and the words under its fields, never colour alone. */
.task--error { border-color: var(--danger); box-shadow: 0 0 0 2px var(--danger-soft); }
.task--error .task-error { font-weight: 500; }
/* Staff entered or corrected this grade: the department, never a person. */
.prq-updated {
  margin: 0;
  padding: 0.5rem 0.75rem;
  border-radius: var(--r-md);
  background: var(--brand-tint);
  font-size: 0.875rem;
  color: var(--ink);
}
.prq-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.75rem; }
.prq-field--wide { grid-column: 1 / -1; }
.prq-optional { font-size: 0.8125rem; font-weight: 400; color: var(--ink-soft); }
/* The selects match the school input beside them. `background-color`, not the
   shorthand the input rule uses: that would also wipe `.field-select`'s chevron.
   A tighter chevron than the console's, so "Summer 2025" fits half a 390px card. */
.public-shell .task .field-select {
  min-height: 46px;
  padding-right: 1.75rem;
  background-color: var(--surface);
  background-position: right 0.5rem center;
  background-size: 1rem;
  font-size: 1rem;
}

/* "Which course did you take?" (2026-10-26, board "Applicant"): a prerequisite
   met by any one of several courses is a radio card per course. The picked card
   is outlined in the brand blue and shows its own credit pills; that reveal is
   CSS alone, so the page still has no script. Where `:has()` is unsupported
   every card shows its pills — the server reads only the picked course's group,
   so a pill left checked under another card is never what is saved. */
.prq-pick { display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; margin: 0; padding: 0; border: 0; }
.prq-pick__legend { margin: 0 0 0.125rem; padding: 0; font-size: 0.875rem; font-weight: 600; color: var(--ink); }
.prq-pick__legend small { display: block; font-size: 0.8125rem; font-weight: 400; color: var(--ink-soft); }
.prq-choice { overflow: hidden; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); }
.prq-choice__label {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  box-sizing: border-box;
  min-height: 44px;
  padding: 0.75rem;
  cursor: pointer;
}
.prq-choice__label input { flex: none; width: 20px; height: 20px; margin: 2px 0 0; accent-color: var(--brand); }
.prq-choice__text { min-width: 0; }
.prq-choice__code { display: block; font-family: var(--font-mono); font-size: 0.8125rem; line-height: 1.3; color: var(--ink-soft); }
.prq-choice__name { display: block; font-size: 0.9375rem; font-weight: 600; line-height: 1.35; color: var(--navy); }
.prq-choice__cr { display: block; margin-top: 2px; font-size: 0.8125rem; color: var(--ink-soft); }
.prq-choice:has(> .prq-choice__label input:checked) {
  border-color: var(--brand);
  box-shadow: 0 0 0 1px var(--brand);
  background: #F7FAFF;
}
.prq-pick--bad .prq-choice { border-color: var(--danger); }
/* Keyboard focus outlines the whole card (or pill); a click does not. */
.prq-choice:has(> .prq-choice__label input:focus-visible) { outline: 2px solid var(--brand); outline-offset: 2px; }

/* Credit pills: under the picked card, or under the heading of a one-course
   prerequisite offering several amounts. */
.prq-credits { min-width: 0; margin: 0; padding: 0 0.75rem 0.75rem 2.75rem; border: 0; }
.prq-credits--solo { padding: 0; }
.prq-credits__legend { margin: 0 0 0.375rem; padding: 0; font-size: 0.875rem; font-weight: 600; color: var(--ink); }
.prq-segs { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.prq-seg {
  display: inline-flex;
  gap: 0.5rem;
  align-items: center;
  box-sizing: border-box;
  min-height: 44px;
  padding: 0.5rem 0.875rem;
  border: 1px solid var(--border-field);
  border-radius: 9999px;
  background: var(--surface);
  font-size: 0.9375rem;
  color: var(--ink);
  cursor: pointer;
}
.prq-seg input { width: 18px; height: 18px; margin: 0; accent-color: var(--brand); }
.prq-seg:has(input:checked) { border-color: var(--brand); background: var(--brand-tint); font-weight: 600; color: var(--navy); }
.prq-seg:has(input:focus-visible) { outline: 2px solid var(--brand); outline-offset: 2px; }
.prq-segs--bad .prq-seg { border-color: var(--danger); }
@supports selector(:has(*)) {
  .prq-choice .prq-credits { display: none; }
  .prq-choice:has(> .prq-choice__label input:checked) .prq-credits { display: block; }
}

/* The page's one button, and what it does. */
.prq-savebar { display: flex; flex-direction: column; gap: 0.5rem; }
.prq-hint { margin: 0; font-size: 0.8125rem; color: var(--ink-soft); }

/* The confirmation page's Prerequisites group: one row (a count, never the
   courses — plan D5) and the minimum GPA under it, inside the group's own gap. */
.todo-note--in-group { margin-top: 0; }

/* ─── Prerequisites: write-ins and the acknowledgement (2026-10-28) ──────────
   Approved board "Applicant" (canvas 2026-10-28), phones 2–5. The grades page
   gains a last radio card per prerequisite, "A course not on this list", whose
   fields show only while it is picked — CSS `:has()` alone, like the credit
   pills above; without `:has()` they simply stay open, and the server reads them
   only when that card is the one posted. The acknowledgement page is a letter,
   the phrase, and the box it is typed into.

   One tint is the board's and has no token: the picked card's #F7FAFF, already
   used by `.prq-choice` above. */

/* "A course not on this list": dashed, and its border stays the field grey even
   when picked or refused — the board's picked card is the 1px brand ring
   outside a grey dashed edge, and a refused pick reddens the listed cards only. */
.prq-choice--own { border-style: dashed; }
.prq-choice.prq-choice--own,
.prq-pick--bad .prq-choice.prq-choice--own,
.prq-choice.prq-choice--own:has(> .prq-choice__label input:checked) { border-color: var(--border-field); }

/* Its code, credits and name: code and credits side by side, the name under them. */
.prq-own {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 7.5rem;
  gap: 0.625rem;
  padding: 0 0.75rem 0.75rem 2.75rem;
}
.prq-own__wide,
.prq-own__note { grid-column: 1 / -1; }
.prq-own__note { margin: 0; font-size: 0.8125rem; color: var(--ink-soft); }
.public-shell .task .prq-own__code { font-family: var(--font-mono); font-size: 0.9375rem; }

/* School (and an optional Term) say "(required for a course you write in)"
   while that card is picked; a form that does not ask them draws them only then. */
.prq-must { font-size: 0.8125rem; font-weight: 500; color: var(--warning-ink); }
.prq-if-own { display: none; }
@supports selector(:has(*)) {
  .prq-choice--own .prq-own { display: none; }
  .prq-choice--own:has(> .prq-choice__label input:checked) .prq-own { display: grid; }
  .prq-only-own { display: none; }
  .task:has(.prq-choice--own > .prq-choice__label input:checked) .prq-only-own { display: block; }
  .task:has(.prq-choice--own > .prq-choice__label input:checked) .prq-grade--alone { grid-column: auto; }
  .task:has(.prq-choice--own > .prq-choice__label input:checked) .prq-if-own { display: inline; }
  .task:has(.prq-choice--own > .prq-choice__label input:checked) .prq-if-listed { display: none; }
}

/* ─── Prerequisites: a listed course's school and logo (2026-10-30) ──────────
   Approved canvas "Forms grades: Colby course logo + locked school". Every state
   of School is drawn and `:has()` shows one, like the write-in fields above: empty
   with nothing picked, the college with a listed course picked, the saved school
   with the course it was saved for picked; `.prq-only-own` above shows the open box.
   Without `:has()` the college (or a kept school) and the open box both show, and the
   server reads the box only for a written-in course. iOS dims a disabled input's text whatever its
   `color`, so the fill and opacity are set too: the applicant has to read this one. */
.prq-logo { display: block; flex: none; width: 44px; height: auto; }
.prq-choice__label .prq-logo { align-self: center; margin-left: auto; }
.task__head .prq-logo { align-self: flex-start; margin-top: 1px; }

.prq-locked { position: relative; }
.public-shell .task .prq-locked .field-input {
  padding-right: 2.5rem;
  border-color: var(--border);
  background: var(--bg);
  color: var(--ink);
  -webkit-text-fill-color: var(--ink);
  opacity: 1;
  cursor: not-allowed;
}
.public-shell .task .prq-locked .field-input::placeholder {
  color: var(--ink-soft);
  -webkit-text-fill-color: var(--ink-soft);
  opacity: 1;
}
.prq-locked__ico {
  position: absolute;
  top: 50%;
  right: 0.75rem;
  width: 18px;
  height: 18px;
  transform: translateY(-50%);
  fill: none;
  stroke: var(--ink-soft);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}
.prq-locked__note { margin: 0.375rem 0 0; font-size: 0.8125rem; line-height: 1.45; color: var(--ink-soft); }

.prq-school--pick .prq-school__state--empty,
.prq-school--kept .prq-school__state--listed { display: none; }
@supports selector(:has(*)) {
  .prq-school--pick .prq-school__state--empty { display: block; }
  .prq-school--pick .prq-school__state--listed,
  .prq-school--pick .prq-school__state--kept { display: none; }
  .task:has(.prq-listed:checked) .prq-school__state--empty,
  .task:has(.prq-choice--own > .prq-choice__label input:checked) .prq-school__state--empty { display: none; }
  .task:has(.prq-listed:checked) .prq-school__state--listed { display: block; }
  .task:has(.prq-keeps:checked) .prq-school__state--listed { display: none; }
  .task:has(.prq-keeps:checked) .prq-school__state--kept { display: block; }
}

/* "Read <Dept>'s message again", between the lede and the GPA box. */
.prq-reread {
  padding: 0.25rem 0.875rem;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  font-size: 0.875rem;
}
.prq-reread > summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  font-weight: 600;
  color: var(--brand);
  list-style: none;
  cursor: pointer;
}
.prq-reread > summary::-webkit-details-marker { display: none; }
.prq-reread > summary:hover { color: var(--navy); text-decoration: underline; }
.prq-reread > summary:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.prq-reread p { margin: 0 0 0.625rem; color: var(--ink); white-space: pre-line; }

/* The acknowledgement: the office's message as a letter. A single newline inside
   a paragraph is kept; a blank line is a new paragraph (the service splits). */
.ack-letter {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  padding: 1.125rem;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
}
.ack-letter__from {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink-soft);
}
.ack-letter__say {
  margin: 0;
  font-size: 1rem;
  line-height: 1.6;
  color: var(--ink);
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* The phrase, and the box it is typed into. */
.ack-type { display: flex; flex-direction: column; gap: 0.5rem; }
.ack-label { display: block; margin: 0; font-size: 0.875rem; font-weight: 600; color: var(--ink); }
.ack-label--box { margin-top: 0.25rem; }
.ack-phrase {
  margin: 0;
  padding: 0.75rem 0.875rem;
  border: 1px solid var(--brand-soft);
  border-radius: 10px;
  background: var(--brand-tint);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.45;
  color: var(--navy);
  overflow-wrap: anywhere;
}
.ack-input { min-height: 46px; padding: 0.625rem 0.75rem; font-size: 1rem; background: var(--surface); }
/* A refused phrase: the red edge and a soft ring, never colour alone (the alert says why). */
.ack-input.is-invalid:not(:focus) { border-color: var(--danger); box-shadow: 0 0 0 2px var(--danger-soft); }
.ack-match {
  display: flex;
  gap: 6px;
  align-items: center;
  margin: 0;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--success);
}
.ack-nopaste {
  margin: 0;
  padding: 0.5rem 0.75rem;
  border-radius: var(--r-md);
  background: var(--warning-soft);
  font-size: 0.875rem;
  color: var(--warning-ink);
}

/* ─── Messages (2026-10-28) ───────────────────────────────────────────────────
   The hub's Messages card and the conversation with the office — approved
   board "Applicant", phones 1–5 — in `upload/dashboard.html`,
   `upload/_messages_*.html` and `upload/messages.html`. Every new class is
   `msgs-`: `.modal`, `.flash`, `.hint`, `.label` and `.btn` already mean other
   things in components.css, and the console's own messaging styles live there.

   The window is a <dialog> (static/js/messages.js opens it). On a phone it is
   the whole screen, a sheet; from 45rem it is a centred dialog. The thread
   scrolls inside it with the newest at the bottom: `column-reverse` on the
   scroll box starts it at the end with no script.

   The board's tints with no token of their own: the thread's ground #F8F9FB;
   the applicant's bubble border #C5DAF9; the "New" rule and ring and the
   links' underline #9DBFF2; the deleted placeholder's dashed #BFC4CC; the
   delete red #B0322A (hover #8E241C, underline #E8A9A3, 5.9:1 on the thread);
   the delete confirmation's border #F0C3BE and its inks #8E241C and #6B2019
   (both over 7:1 on --danger-soft). #0B5E49 is the all-set ink above. */

/* The card: the board's outlined (secondary) button, and the dot by "1 new". */
.dash-btn--outline { background: var(--surface); color: var(--brand); border-color: var(--brand); }
.dash-btn--outline:hover { background: var(--brand-tint); color: var(--navy); border-color: var(--navy); }
.dash-btn--danger { background: #B0322A; border-color: #B0322A; }
.dash-btn--danger:hover { background: #8E241C; border-color: #8E241C; }
.msgs-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  margin-right: 6px;
  border-radius: 50%;
  background: var(--brand);
  vertical-align: 1px;
}

/* The slot generates no box, so an empty one opens no gap in the hub's column. */
.msgs-slot { display: contents; }

/* ── The window ── */
.msgs-dialog {
  width: 100%;
  max-width: none;
  height: 100vh;
  height: 100dvh;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  overflow: hidden;
  background: var(--surface);
  color: var(--ink);
  font-size: 1rem;
  line-height: 1.55;
}
.msgs-dialog[open] { display: flex; flex-direction: column; }
.msgs-dialog::backdrop { background: rgba(1, 33, 75, 0.42); }
html:has(.msgs-dialog[open]) { overflow: hidden; }
@media (min-width: 45rem) {
  .msgs-dialog {
    width: min(40rem, calc(100vw - 2rem));
    height: min(50rem, calc(100vh - 4rem));
    margin: auto;
    border: 1px solid var(--border);
    border-radius: 14px;
    box-shadow: var(--shadow-modal);
  }
}
@media (prefers-reduced-motion: no-preference) {
  .msgs-dialog[open] { animation: modal-rise 160ms cubic-bezier(.2, .7, .2, 1); }
  .msgs-dialog[open]::backdrop { animation: modal-fade 160ms ease; }
}

/* The page without JavaScript: the same sheet, in the reading column, its
   thread a box of its own so the box to write in stays in view. */
.msgs-sheet {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
}
.msgs-sheet .msgs-scroll { max-height: min(34rem, 65vh); }

.msgs-head {
  display: flex;
  flex: none;
  gap: 10px;
  align-items: flex-start;
  padding: 14px 14px 12px 20px;
  border-bottom: 1px solid var(--border);
}
.msgs-head__text { min-width: 0; }
.msgs-title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 21px;
  font-weight: 600;
  line-height: 28px;
  color: var(--navy);
}
.msgs-sub { margin: 0; font-size: 13.5px; color: var(--ink-soft); }
.msgs-ref { font-family: var(--font-mono); }
.msgs-x {
  display: grid;
  place-items: center;
  flex: none;
  width: 44px;
  height: 44px;
  margin-left: auto;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.msgs-x:hover { background: var(--brand-tint); border-color: var(--brand); }
/* A refusal htmx did not swap (messages.js writes it): under the head. */
.msgs-alert {
  flex: none;
  margin: 0;
  padding: 10px 20px;
  border-bottom: 1px solid #F0C3BE;
  background: var(--danger-soft);
  font-size: 14px;
  font-weight: 500;
  color: var(--danger-ink);
}

.msgs-body { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; }

/* ── The thread ── */
.msgs-scroll {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column-reverse;
  min-height: 0;
  overflow-y: auto;
  background: #F8F9FB;
}
.msgs-scroll:focus-visible { outline-offset: -2px; border-radius: 0; }
.msgs-thread {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin: 0;
  padding: 16px 16px 18px;
  list-style: none;
}
.msgs-row { display: flex; flex-direction: column; gap: 14px; scroll-margin-top: 16px; }
.msgs-new {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  font-weight: 600;
  line-height: 16px;
  color: var(--brand);
}
.msgs-new::before,
.msgs-new::after { content: ""; flex: 1; height: 1px; background: #9DBFF2; }
.msgs-msg { display: flex; flex-direction: column; gap: 5px; max-width: 88%; }
.msgs-msg--them { align-self: flex-start; align-items: flex-start; }
.msgs-msg--us { align-self: flex-end; align-items: flex-end; }
.msgs-msg--editing { max-width: 100%; }
.msgs-by {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 6px;
  margin: 0;
  font-size: 13px;
  line-height: 18px;
  color: var(--ink-soft);
}
.msgs-by b { font-weight: 600; color: var(--ink); }
/* Plain text: line breaks kept, long words and URLs broken anywhere. */
.msgs-bubble {
  margin: 0;
  padding: 11px 13px;
  border-radius: 14px;
  font-size: 15.5px;
  line-height: 23px;
  color: var(--ink);
  white-space: pre-line;
  overflow-wrap: anywhere;
}
.msgs-bubble--them { background: var(--surface); border: 1px solid var(--border); border-top-left-radius: 4px; }
.msgs-bubble--us { background: var(--brand-tint); border: 1px solid #C5DAF9; border-top-right-radius: 4px; }
.msgs-bubble--new { box-shadow: 0 0 0 2px #9DBFF2; }
.msgs-gone {
  margin: 0;
  padding: 9px 13px;
  border: 1px dashed #BFC4CC;
  border-radius: 14px;
  font-size: 14.5px;
  font-style: italic;
  color: var(--ink-soft);
}
.msgs-empty { margin: auto 0; padding: 28px 12px; text-align: center; font-size: 15px; color: var(--ink-soft); }

/* "Edited", Edit and Delete under a message: each link a 44px target. */
.msgs-tools { display: flex; align-items: center; gap: 4px; font-size: 13px; color: var(--ink-soft); }
.msgs-edited { border-bottom: 1px dotted var(--border-field); }
.msgs-lnk {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 8px;
  border: 0;
  background: none;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--brand);
  text-decoration: underline;
  text-decoration-color: #9DBFF2;
  text-underline-offset: 3px;
  cursor: pointer;
}
.msgs-lnk:hover { color: var(--navy); text-decoration-color: currentColor; }
.msgs-lnk--danger { color: #B0322A; text-decoration-color: #E8A9A3; }
.msgs-lnk--danger:hover { color: #8E241C; }

/* Editing in place, and deleting with a confirmation in place. */
.msgs-edit { display: flex; flex-direction: column; gap: 6px; width: 330px; max-width: 100%; }
.msgs-pair { display: flex; flex-wrap: wrap; gap: 8px; }
.dash-btn.msgs-btn-sm { width: auto; min-height: 44px; padding: 8px 14px; font-size: 15px; }
.msgs-confirm {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border: 1px solid #F0C3BE;
  border-radius: 12px;
  background: var(--danger-soft);
}
.msgs-confirm__q { margin: 0; font-size: 15px; font-weight: 600; color: #8E241C; }
.msgs-confirm__hint { margin: 0; font-size: 13px; color: #6B2019; }

/* ── The box to write in, pinned under the thread ── */
.msgs-compose {
  display: flex;
  flex: none;
  flex-direction: column;
  gap: 8px;
  padding: 12px 16px 16px;
  border-top: 1px solid var(--border);
  background: var(--surface);
}
.msgs-label { display: block; margin: 0; font-size: 14px; font-weight: 600; color: var(--ink); }
/* 16px: below that, iOS zooms the page when the box takes focus. */
.msgs-ta {
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border-field);
  border-radius: 10px;
  background: var(--surface);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 23px;
  color: var(--ink);
  resize: vertical;
}
.msgs-ta--bad { border-color: var(--danger); box-shadow: 0 0 0 2px var(--danger-soft); }
.msgs-err { margin: 0; font-size: 14px; font-weight: 500; color: var(--danger-ink); }
.msgs-hint { margin: 0; font-size: 13px; color: var(--ink-soft); }
.msgs-flash {
  margin: 0;
  padding: 8px 12px;
  border-radius: 10px;
  background: var(--success-soft);
  font-size: 14px;
  color: #0B5E49;
}
/* Archived: read only (phone 4). */
.msgs-closed {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin: 0;
  padding: 12px;
  border-radius: 10px;
  background: var(--divider);
  font-size: 14.5px;
  line-height: 21px;
  color: var(--ink);
}
.msgs-closed .dash-ico { margin-top: 2px; }
/* Where focus is put after a swap so a screen reader reads it — not controls,
   so no ring. */
.msgs-flash:focus,
.msgs-closed:focus,
.msgs-bubble:focus { outline: none; }
.msgs-lnk:focus-visible,
.msgs-x:focus-visible,
.msgs-ta:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* ─── Prerequisites: one another makes "not needed" (2026-10-29) ─────────────
   Approved boards "Applicant-page" (the grades page on a phone) and
   "Applicant-states" (the hub card, the first visit, a kept grade); their
   <helmet> rules lifted as they are, `cp-ico` renamed `prq-ico`
   (`.prq-skip-kept` takes the states board's line-height). A card with a rule
   is drawn in both states at once; the ONE class `task--skip` on it picks
   which, and hides its controls — `display: none` only, so each part keeps its
   own display when `static/js/prereq-rules.js` takes the class off again. */
.prq-ico{flex:none;width:15px;height:15px}
.prq-rule-hint{display:flex;gap:0.5rem;align-items:flex-start;margin:0;padding:0.5rem 0.75rem;border-radius:var(--r-md);background:var(--bg);font-size:0.875rem;line-height:1.45;color:var(--ink)}
.prq-rule-hint svg{margin-top:2px;color:var(--brand)}
.task.task--skip{gap:0.5rem;padding:0.875rem 1rem;border-style:dashed;border-color:var(--border-strong);background:var(--bg);box-shadow:none}
.task--skip .task__title{color:var(--ink)}
.task__glyph.task__glyph--skip{border:2px solid var(--border-strong);background:var(--surface);color:var(--ink-soft)}
.task__glyph--skip svg{width:13px;height:13px}
.task__pill.task__pill--skip{border:1px dashed var(--border-field);background:var(--surface);color:var(--navy)}
.prq-skip-why{margin:0;font-size:0.9375rem;line-height:1.5;color:var(--ink)}
.prq-skip-kept{margin:0;font-size:0.8125rem;line-height:1.45;color:var(--ink-soft)}
.dash-card__skip{margin:-0.375rem 0 0;font-size:0.875rem;color:var(--ink-soft)}

/* Needed: the glyph, pills and hint; not needed: the skip glyph, the
   "Not needed" pill and the sentences — and none of the controls. */
.task:not(.task--skip) .prq-if-skip,
.task--skip .prq-if-needed,
.task--skip .prq-pick,
.task--skip .prq-credits,
.task--skip .prq-updated,
.task--skip .prq-fields,
.task--skip .task-error { display: none; }


/* Where the application stands (2026-10-31, board "Applicant"). One card under the
   greeting; a three-stop track with a word and a date on every stop, then the state
   and its sentence. Approved wears the all-set green. Declined is gray, never red
   (plan D12). A stop's line runs from its own dot towards the next stop's. */
.dash-rv {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  padding: 1.125rem;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
}
.dash-rv--ok { background: var(--success-soft); border-color: #BFE3D6; }
.dash-rv--no { background: #EEF1F5; }
.dash-rv__track {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}
.dash-rv__stop {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  min-width: 0;
  font-size: 0.8125rem;
  line-height: 1.3;
  color: var(--ink-soft);
}
.dash-rv__stop::before {
  content: "";
  position: absolute;
  top: 8px;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--brand-soft);
}
.dash-rv__stop:first-child::before { left: 9px; }
.dash-rv__stop:last-child::before { right: calc(100% - 9px); }
.dash-rv__stop--on::before { background: var(--brand); }
.dash-rv__stop b { font-weight: 600; color: var(--ink); }
.dash-rv__stop--off b { font-weight: 500; color: var(--ink-soft); }
.dash-rv__dot {
  position: relative;
  box-sizing: border-box;
  width: 19px;
  height: 19px;
  border: 3px solid var(--brand-soft);
  border-radius: 50%;
  background: var(--surface);
}
.dash-rv__dot--past { background: var(--brand); border-color: var(--brand); }
.dash-rv__dot--now { background: var(--surface); border: 5px solid var(--brand); }
.dash-rv__dot--ok { background: var(--success); border-color: var(--success); }
.dash-rv__dot--no { background: #4A5563; border-color: #4A5563; }
.dash-rv__words { display: flex; flex-direction: column; gap: 0.375rem; }
.dash-rv__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.375rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--navy);
}
.dash-rv--ok .dash-rv__title { color: #0B5E49; }
.dash-rv__text { margin: 0; font-size: 0.9375rem; line-height: 1.5; color: var(--ink); }
.dash-rv__when { margin: 0; font-size: 0.8125rem; color: var(--ink-soft); }
.dash-rv__more { margin: 4px 0 0; font-size: 0.875rem; line-height: 1.45; font-weight: 600; color: #0B5E49; }
.dash-tile--quiet { background: var(--divider); color: #4A5563; }

/* Letters of recommendation (2026-11-05, rendition "Forms: Letters of
   Recommendation", boards "Applicant" and "Recommender: the upload page"). */
.lor-reads { margin: 0; }
.lor-ask { margin: 4px 0 -4px; font-size: 0.9375rem; color: var(--navy); }
.lor-count { display: flex; justify-content: space-between; gap: 8px; margin-top: 4px; font-size: 0.8125rem; color: var(--ink-soft); }
.lor-count__n { font-family: var(--font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.lor-count__n.is-near { color: var(--warning-ink); font-weight: 600; }
.lor-msg { margin: 4px 0 0; padding: 8px 10px; border-radius: 8px; background: var(--bg); font-size: 0.8125rem; line-height: 1.45; color: var(--ink); white-space: pre-line; overflow-wrap: anywhere; }
.lor-msg__lbl { display: block; margin-bottom: 2px; font-size: 0.75rem; font-weight: 600; color: var(--ink-soft); }
.lor-msg--page { margin: 0; padding: 10px 12px; font-size: 0.9375rem; line-height: 1.5; }
.lor-who { display: flex; gap: 10px; align-items: flex-start; margin: 0; padding: 10px 12px; border-radius: 10px; background: var(--brand-tint); font-size: 0.875rem; line-height: 1.45; color: var(--navy); }
.lor-who svg { flex: none; width: 18px; height: 18px; margin-top: 1px; }
.lor-facts { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; margin: 0; font-size: 0.875rem; }
.lor-facts dt { color: var(--ink-soft); }
.lor-facts dd { margin: 0; color: var(--ink); font-weight: 500; overflow-wrap: anywhere; }
.lor-done { align-items: flex-start; gap: 10px; }
.lor-done .dash-h1 { margin: 0; }
.lor-done__mark { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%; background: var(--success); color: var(--on-success, #FFFFFF); }
.lor-done__mark--off { background: var(--surface-2); color: var(--ink-soft); }
