/* The New York Performing Arts Academy — tuition installment portal.
   Design tokens and every style used by the pages in this directory.
   No CDN, no build step, no framework.

   House style: Obsidian (#151515), Corporate Gold (#B9912F), Ivory (#F7F4EE),
   Libre Baskerville headings over IBM Plex Sans body, IBM Plex Mono figures.

   Two themes, one component set. Public pages (index, enter, pay, portal,
   agreement, 404) render light: ivory paper, white surfaces, gold accents.
   Staff pages carry `class="staff"` on <body> and render dark: obsidian paper,
   raised graphite surfaces, the same gold. Nothing below duplicates a component
   for the dark theme — `body.staff` only re-points the tokens, so anything that
   looks right on one theme looks right on the other. */

/* Self-hosted Latin subsets, variable weight. Generated from Google Fonts and
   served from this server: no CDN request is made at runtime, so the pages keep
   working on a locked-down network and nothing about a family's visit leaves the
   box. Each face declares a weight range because the downloaded file is a
   variable font. If a file is ever missing the stack in base.css falls back to a
   system serif / sans / mono and the page still renders. */

@font-face {
  font-family: 'Libre Baskerville';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/LibreBaskerville-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 100 700;
  font-display: swap;
  src: url('/assets/fonts/IBMPlexSans-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 100 700;
  font-display: swap;
  src: url('/assets/fonts/IBMPlexMono-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Ink is the darkest structural colour: headings, hard borders, emphasis. */
  --ink: #151515;
  --ink-soft: #33383F;
  --gold: #B9912F;
  /* Gold as *text* only ever appears in this darker cut: #B9912F on ivory is
     around 3:1, which is fine for a rule, a border or a large figure and not
     fine for words. --gold-ink is the one used for words on a light surface. */
  --gold-ink: #7A6026;
  /* Gold used as a fill needs its own hover, and a tint for soft backgrounds. */
  --gold-strong: #A07C24;
  --gold-wash: #F6F0E1;
  --paper: #F7F4EE;
  --surface: #FFFFFF;
  --surface-2: #FBF9F5;
  --line: #E3DED4;
  --line-soft: #EDE9E1;
  --text: #1A1D22;
  --muted: #5C6470;
  --settled: #2F6B4F;
  --pending: #8A6A1F;
  --overdue: #8C2F2F;

  /* Buttons. Primary is a gold fill with obsidian type — the house accent doing
     the work, and around 7:1 either way round, so it passes on a payment page. */
  --btn-bg: var(--gold);
  --btn-fg: #151515;
  --btn-hover-bg: var(--gold-strong);
  --btn-quiet-hover: var(--gold-wash);
  --btn-off-bg: #EDEAE3;
  --btn-off-fg: #4A505A;

  /* The focus ring, as a colour and as a soft outer glow. */
  --ring: var(--gold-ink);   /* 3.9:1 on ivory; var(--gold) measured 2.67:1, under WCAG 1.4.11 */
  --ring-wash: rgb(185 145 47 / 22%);

  --serif: 'Libre Baskerville', ui-serif, Georgia, 'Times New Roman', serif;
  --sans: 'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --measure: 68rem;
  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 0.75rem;
  --s4: 1rem;
  --s5: 1.5rem;
  --s6: 2rem;
  --s7: 3rem;
  --radius: 4px;
}

/* ------------------------------------------------------- staff dark theme */

/* Token re-points only. Every rule further down reads these, so the whole
   component set flips with this block and there is no second copy of a card, a
   table or a pill to keep in step. Status and state colours are lightened
   because the reds and greens tuned for ivory go muddy on obsidian. */
body.staff {
  --ink: #F7F4EE;
  --ink-soft: #D9D3C7;
  --gold: #C9A245;
  --gold-ink: #DCBB63;
  --gold-strong: #D8B355;
  --gold-wash: #262117;
  --paper: #151515;
  --surface: #1C1C1E;
  --surface-2: #232326;
  --line: #33322E;
  --line-soft: #2A2A28;
  --text: #F7F4EE;
  --muted: #A19C92;
  --settled: #74C295;
  --pending: #DCB454;
  --overdue: #EC8A84;

  --btn-bg: var(--gold);
  --btn-fg: #151515;
  --btn-hover-bg: #D8B355;
  --btn-quiet-hover: #262626;
  --btn-off-bg: #2A2A2C;
  --btn-off-fg: #8E8A82;

  --ring: #C9A245;
  --ring-wash: rgb(201 162 69 / 28%);

  color-scheme: dark;
}


*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--text);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.55;
}

/* Anything that is a figure or an identifier is monospaced with tabular
   numerals, so columns line up and a reference read down a phone line is
   unambiguous. */
/* The one place a form sits on a line with its own submit: the horizon
   selector on the cash forecast. */
.inlineform { display: flex; flex-wrap: wrap; gap: var(--s4); align-items: flex-end; }
.inlineform .field { margin-bottom: 0; }
.inlineform .actions { margin: 0; }
input.num { max-width: 9rem; }

.num, .money, .ref, code, td.money, th.money {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

h1, h2, h3 { font-family: var(--serif); font-weight: 600; line-height: 1.2; color: var(--ink); }
h1 { font-size: 1.75rem; margin: 0 0 var(--s2); }
h2 { font-size: 1.25rem; margin: var(--s6) 0 var(--s3); }
h3 { font-size: 1.05rem; margin: var(--s5) 0 var(--s2); }
p { margin: 0 0 var(--s4); }
a { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
a:hover { color: var(--gold-ink); }

:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 2px;
}

/* ------------------------------------------------------------------ shell */

/* One column width per page, declared on <body>, so the masthead, the content
   and the footer share a left edge. Before this existed the narrow pages put a
   34rem card inside a 68rem masthead, which read as a misaligned page rather
   than a deliberately narrow one. Keyed off body classes and not :has() so
   there is no browser-support question on a page that takes money. */
body { --column: var(--measure); }
body.narrow { --column: 34rem; }
body.staff  { --column: 60rem; }

.masthead {
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}
.masthead-inner {
  max-width: var(--column);
  margin: 0 auto;
  padding: var(--s4) var(--s5);
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
}
.masthead .brand {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  align-items: flex-start;
}
/* Intrinsic width/height are on the <img> so the header does not reflow while
   the logo loads. The declared width here is the one that governs. */
.masthead .logo {
  display: block;
  width: 240px;
  height: auto;
  flex: none;
}
.masthead .brand-sub {
  display: block;
  font-family: var(--sans);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
@media (max-width: 40rem) {
  .masthead .logo { width: 190px; }
}
.masthead nav { margin-left: auto; font-size: 0.875rem; }

main {
  max-width: var(--column);
  margin: 0 auto;
  padding: var(--s6) var(--s5) var(--s7);
}

footer.pagefoot {
  max-width: var(--column);
  margin: 0 auto;
  padding: var(--s5);
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.8125rem;
}
footer.pagefoot p { margin: 0 0 var(--s2); }
/* The policy line. Underlined so it reads as a link at 13px, where colour alone
   is not enough of a signal, and in the muted footer colour so it does not
   compete with the pay button above it. */
footer.pagefoot p.legal a { color: var(--muted); text-decoration: underline; }
footer.pagefoot p.legal a:hover,
footer.pagefoot p.legal a:focus-visible { color: var(--ink); }

/* ------------------------------------------------------------------ cards */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s5);
  margin: 0 0 var(--s5);
}
.card > :last-child { margin-bottom: 0; }
.card-head {
  display: flex;
  gap: var(--s3);
  align-items: baseline;
  flex-wrap: wrap;
  margin-bottom: var(--s3);
}
.card-head h2, .card-head h1 { margin: 0; }
.rule { border: 0; border-top: 1px solid var(--line); margin: var(--s5) 0; }
.rule-gold { border-top: 2px solid var(--gold); }

.eyebrow {
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 var(--s1);
}
.lede { color: var(--ink-soft); }
.fine { font-size: 0.8125rem; color: var(--muted); }
.muted { color: var(--muted); }

/* Key/value pairs. Two columns on a wide screen, stacked on a phone and
   inside a 400px HubSpot card. */
.pairs { display: grid; gap: var(--s3) var(--s5); grid-template-columns: 1fr 1fr; margin: 0; }
.pairs > div { min-width: 0; }
/* The address panel puts prose directly beneath its figures, and .pairs has no
   bottom margin of its own — without this the warning about what a correction
   does sits flush against the link count and reads as part of it. */
#emailbody .pairs { margin-bottom: var(--s4); }
/* A subtraction has to read downwards. In two columns "tuition less deposit
   equals financed" wraps across the gutter and stops being arithmetic, so a
   money column that a director is meant to check line by line opts out of the
   grid entirely: label left, figure right, one row per line, at every width. */
.pairs.ledger {
  grid-template-columns: 1fr;
  gap: var(--s2);
  max-width: 30rem;
  margin-top: var(--s5);
}
.pairs.ledger > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
}
.pairs.ledger dt { font-size: 0.9375rem; color: var(--ink); }
.pairs.ledger dd { text-align: right; white-space: nowrap; }
/* The line the whole panel exists for gets a rule above it, the way a total
   does on paper. */
.pairs.ledger > div.total {
  border-top: 1px solid var(--line);
  padding-top: var(--s2);
  margin-top: var(--s1);
}
/* Prose after a total is a separate thought, not the total's second line. */
.pairs.ledger + .fine { margin-top: var(--s4); }
.pairs dt { font-size: 0.8125rem; color: var(--muted); margin: 0; }
.pairs dd { margin: 0; overflow-wrap: anywhere; }
.pairs dd.money, .pairs dd .money { font-family: var(--mono); font-variant-numeric: tabular-nums; }
/* A qualifier under a figure — "returnable for 60 more days" — is its own line,
   never run on to the number it qualifies. */
.pairs dd .sub {
  display: block;
  font-family: var(--sans);
  font-size: 0.8125rem;
  color: var(--muted);
}

.figure {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 1.5rem;
  color: var(--ink);
}

/* ------------------------------------------------------------------ status */

/* Never colour alone: every pill carries the word, and a shape prefix that
   survives a greyscale print or a colour-blind reader. */
.status {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35em;
  font-family: var(--sans);
  font-size: 0.8125rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.1em 0.5em;
  background: var(--surface);
  color: var(--text);
  white-space: nowrap;
}
.status::before { font-family: var(--mono); font-size: 0.875em; }
.status-paid, .status-settled { color: var(--settled); border-color: var(--settled); }
.status-paid::before, .status-settled::before { content: "\2713"; }
/* `active` and `on hold` are aliases, not new looks: a plan quietly running is
   the same visual weight as a scheduled installment, and a hold reads like
   anything else waiting on a person. Adding a colour for every word is how a
   table stops having a hierarchy. */
.status-scheduled, .status-active { color: var(--muted); }
.status-scheduled::before, .status-active::before { content: "\00B7"; }
.status-pending, .status-provisional, .status-on-hold { color: var(--pending); border-color: var(--pending); }
.status-pending::before, .status-provisional::before,
.status-on-hold::before { content: "\25CB"; }
.status-overdue, .status-failed, .status-past-due { color: var(--overdue); border-color: var(--overdue); font-weight: 600; }
.status-overdue::before, .status-failed::before,
.status-past-due::before { content: "\0021"; }
.status-cancelled { color: var(--muted); text-decoration: line-through; }
.status-cancelled::before { content: "\2013"; }
.status-urgent { color: var(--overdue); border-color: var(--overdue); font-weight: 600; }
.status-urgent::before { content: "\0021"; }

/* ------------------------------------------------------------------ tables */

.tablewrap { overflow-x: auto; margin: 0 0 var(--s4); }
table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}
caption {
  caption-side: top;
  text-align: left;
  font-size: 0.8125rem;
  color: var(--muted);
  padding-bottom: var(--s2);
}
th, td {
  text-align: left;
  padding: var(--s3) var(--s3);
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
thead th {
  border-bottom: 2px solid var(--gold);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
}
th.money, td.money { text-align: right; white-space: nowrap; }
tbody tr:last-child td { border-bottom: 1px solid var(--line); }
td .sub { display: block; font-size: 0.8125rem; color: var(--muted); }

/* ------------------------------------------------------------------ forms */

form { margin: 0; }
.field { margin: 0 0 var(--s4); }
.field label, .fieldset-legend { display: block; font-size: 0.875rem; margin-bottom: var(--s1); color: var(--ink); }
.field .hint { display: block; font-size: 0.8125rem; color: var(--muted); margin-top: var(--s1); }
/* File inputs. The first one in the application is the Affirm-decline upload, and
   an unstyled one sits on the text baseline with the native button jammed against
   whatever precedes it. Block, with room under it, so the field reads as a field. */
input[type="file"] {
  display: block;
  width: 100%;
  max-width: 26rem;
  font: inherit;
  font-size: 0.9375rem;
  color: var(--ink);
  padding: var(--s2) 0;
}
input[type="file"]::file-selector-button {
  font: inherit;
  font-size: 0.875rem;
  margin-right: var(--s3);
  padding: var(--s2) var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover { border-color: var(--ink); }
input[type="file"]:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
input[type="file"]:disabled { color: var(--muted); cursor: default; }
input[type="file"]:disabled::file-selector-button { cursor: default; opacity: 0.6; }
/* `search` is here because a search box that is not styled is a raw browser
   control on a dark page: white, undersized and nothing like the rest. */
input[type="email"], input[type="text"], input[type="number"],
input[type="date"], input[type="search"], textarea, select {
  width: 100%;
  font-family: var(--sans);
  font-size: 1rem;
  padding: 0.6rem 0.7rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--muted);
  border-radius: var(--radius);
}
input.initials { font-family: var(--mono); text-transform: uppercase; max-width: 10rem; }
textarea { min-height: 5rem; resize: vertical; }
fieldset { border: 1px solid var(--line); border-radius: var(--radius); padding: var(--s4); margin: 0 0 var(--s4); }
/* The Affirm step: a button and whatever the server answered, standing between
   the settlement sentence above it and the plan fieldset below. It had no rule
   at all, so the button's own box was the only space around it and its bottom
   edge sat on the fieldset's legend. The bottom margin is the larger of the two
   because the border below reads as a new section starting. */
.stack { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s3); margin: var(--s4) 0 var(--s5); }
legend { padding: 0 var(--s2); font-size: 0.875rem; color: var(--ink); }

.choice {
  display: flex;
  gap: var(--s3);
  align-items: flex-start;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s3);
  margin-bottom: var(--s2);
  cursor: pointer;
}
.choice input { margin: 0.2rem 0 0; flex: none; width: 1.05rem; height: 1.05rem; accent-color: var(--ink); }
.choice:has(input:checked) { border-color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink); }
.choice .choice-title { display: block; font-weight: 600; }
.choice .choice-body { display: block; margin-top: 0.2rem; font-size: 0.875rem; color: var(--muted); }

.check { display: flex; gap: var(--s3); align-items: flex-start; margin-bottom: var(--s4); }
.check input { margin: 0.2rem 0 0; flex: none; width: 1.05rem; height: 1.05rem; accent-color: var(--ink); }
.check label { font-size: 0.9375rem; }

/* Primary is the house gold with obsidian type. Weight 600 and a little letter
   spacing because a gold fill is softer than a black one and the label has to
   hold its own on it. */
button, .btn {
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.2;
  padding: 0.65rem 1.1rem;
  border-radius: var(--radius);
  border: 1px solid var(--btn-bg);
  background: var(--btn-bg);
  color: var(--btn-fg);
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}
button:hover, .btn:hover {
  background: var(--btn-hover-bg);
  border-color: var(--btn-hover-bg);
  color: var(--btn-fg);
}
button.secondary, .btn.secondary {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--gold);
}
button.secondary:hover, .btn.secondary:hover { background: var(--btn-quiet-hover); color: var(--ink); }
button.quiet {
  background: transparent;
  border-color: var(--muted);
  color: var(--ink);
}
button.quiet:hover { background: var(--btn-quiet-hover); color: var(--ink); border-color: var(--muted); }
button[disabled], button[aria-disabled="true"],
button[disabled]:hover, button[aria-disabled="true"]:hover {
  opacity: 1;
  background: var(--btn-off-bg);
  border-color: var(--line);
  color: var(--btn-off-fg);
  cursor: not-allowed;
}
.actions { display: flex; gap: var(--s3); flex-wrap: wrap; align-items: center; }


/* ------------------------------------------------------- notices and state */

.notice {
  border: 1px solid var(--line);
  border-left: 4px solid var(--gold);
  background: var(--surface);
  padding: var(--s4);
  border-radius: var(--radius);
  margin: 0 0 var(--s5);
}
.notice > :last-child { margin-bottom: 0; }
.notice-title { font-weight: 600; margin: 0 0 var(--s1); }
.notice-error { border-left-color: var(--overdue); }
.notice-error .notice-title { color: var(--overdue); }
.notice-ok { border-left-color: var(--settled); }
.notice-ok .notice-title { color: var(--settled); }
.notice-warn { border-left-color: var(--pending); }
.notice-warn .notice-title { color: var(--pending); }
/* Work in progress. The base gold rule already renders this; named so a kind
   passed from a page module always has a rule behind it. */
.notice-info { border-left-color: var(--gold); }
.notice ul { margin: 0; padding-left: 1.2rem; }
.notice li { margin-bottom: var(--s2); }

.skeleton { color: var(--muted); font-size: 0.9375rem; }
.empty {
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  padding: var(--s4);
  color: var(--muted);
  font-size: 0.9375rem;
}
[hidden] { display: none !important; }

.sronly {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skiplink {
  position: absolute; left: -9999px; top: 0; background: var(--surface);
  padding: var(--s2) var(--s3); border: 1px solid var(--ink); z-index: 10;
}
.skiplink:focus { left: var(--s3); top: var(--s3); }

/* The two staff pages run inside a 400px HubSpot card where a display <h1>
   would eat a third of the visible height, so their document title is set as
   a heading a screen reader announces and the eye never sees. */
.vh {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* ------------------------------------------------------- disclosure frame */

.docframe {
  width: 100%;
  min-height: 30rem;
  border: 1px solid var(--line);
  background: var(--surface);
}

/* ------------------------------------------------------------ staff shell */

/* Staff pages render inside a 400px HubSpot card as well as standalone. The
   card layout is the base and the wide layout is the enhancement, so nothing
   depends on there being room. */
body.staff { background: var(--surface); }
body.staff main { padding: var(--s4); }  /* width comes from --column */
body.staff .masthead-inner { padding: var(--s3) var(--s4); }
body.staff h1 { font-size: 1.25rem; }
body.staff h2 { font-size: 1.05rem; margin-top: var(--s5); }
body.staff .card { padding: var(--s4); margin-bottom: var(--s4); }
body.staff table { font-size: 0.875rem; }
body.staff th, body.staff td { padding: var(--s2); }

.queue-total {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  flex-wrap: wrap;
}
.queue-total .figure { font-size: 2rem; }

/* A table wider than its card scrolls inside the card, and the container is
   focusable so a keyboard can reach the far columns. */
.tablewrap:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.itemlist { list-style: none; margin: 0; padding: 0; }
.itemlist > li {
  border-top: 1px solid var(--line);
  padding: var(--s3) 0;
}
.itemlist > li:first-child { border-top: 0; }
.itemrow { display: flex; gap: var(--s3); justify-content: space-between; flex-wrap: wrap; }
.itemrow .who { min-width: 0; flex: 1 1 18rem; }
.itemrow .who strong { display: block; }
/* Each fact about an item gets its own line: staff read these in a 400px card,
   where a run-on string of reference, programme and dates is unreadable. */
.itemrow .who > span,
.itemrow .who > div { display: block; }
.itemrow .who > span + span { margin-top: 0.1rem; }
.itemrow .who .status { display: inline-block; }
.itemrow .amt {
  flex: none;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}
.itemrow .amt > span { display: block; }
.itemrow .amt .sub {
  font-family: var(--sans);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  color: var(--muted);
}

/* ------------------------------------------------------------ responsive */

@media (max-width: 52rem) {
  .pairs { grid-template-columns: 1fr; }
  main { padding: var(--s5) var(--s4) var(--s6); }
  h1 { font-size: 1.5rem; }
}

@media (max-width: 30rem) {
  .masthead-inner { padding: var(--s3) var(--s4); }
  .masthead nav { margin-left: 0; width: 100%; }
  .card { padding: var(--s4); }
  table { font-size: 0.875rem; }
  th, td { padding: var(--s2); }
  .actions button, .actions .btn { width: 100%; text-align: center; }
  .figure { font-size: 1.25rem; }
}

/* Print. The agreement, the disclosures and the schedule get printed and filed,
   and a staff page gets printed off the dark console, so print resets both
   themes to black on white rather than sending a page of obsidian to the tray.
   Tokens are re-pointed here for the same reason the dark theme re-points them:
   one place, and every component follows. */
@media print {
  :root, body, body.staff {
    --paper: #FFFFFF;
    --surface: #FFFFFF;
    --surface-2: #FFFFFF;
    --text: #000000;
    --ink: #000000;
    --ink-soft: #222222;
    --muted: #444444;
    --line: #BBBBBB;
    --line-soft: #DDDDDD;
    --gold: #7A6026;
    --gold-ink: #7A6026;
    --gold-wash: #FFFFFF;
    --settled: #1F5138;
    --pending: #6A5215;
    --overdue: #7A2020;
    color-scheme: light;
  }
  body { background: #FFFFFF; color: #000000; }
  .masthead nav, .actions, form { display: none; }
  .screen-only { display: none !important; }
  .print-only { display: revert !important; }
  .card, .notice, table, .planopt, .itemlist > li { break-inside: avoid; }
}


/* Paired assets for a page that is meant to be printed: see the print block above.
   Hidden rather than absent on screen so the printed sheet needs no second request. */
.print-only { display: none; }

/* ------------------------------------------------- tables on a narrow screen */

/* Below this width a money table cannot show six columns, and the one that
   matters most — what we actually collect — is the rightmost, so sideways
   scrolling hides it. Stack each row instead and print the column name beside
   each figure, from the data-label that api.js stamps on every cell.

   The figures stay right-aligned and tabular so a column of amounts still reads
   as a column. The scroll container keeps its role and tabindex; it simply has
   nothing left to scroll. */
@media (max-width: 34rem) {
  .tablewrap table,
  .tablewrap thead,
  .tablewrap tbody,
  .tablewrap tr,
  .tablewrap th,
  .tablewrap td {
    display: block;
  }

  /* The header row is redundant once every cell carries its own label, and it is
     visually hidden rather than removed so the table keeps its accessible shape. */
  .tablewrap thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .tablewrap tbody tr {
    border-top: 1px solid var(--line);
    padding: var(--s3) 0;
  }
  .tablewrap tbody tr:first-child { border-top: 0; }

  .tablewrap tbody td,
  .tablewrap tbody th {
    border: 0;
    padding: 0.15rem 0;
    text-align: left;
  }

  /* Label on the left, figure on the right, on one line each. */
  .tablewrap tbody td[data-label] {
    display: flex;
    gap: var(--s3);
    justify-content: space-between;
    align-items: baseline;
  }
  .tablewrap tbody td[data-label]::before {
    content: attr(data-label);
    flex: none;
    color: var(--muted);
    font-size: 0.8125rem;
  }
  .tablewrap tbody td.money {
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    text-align: right;
  }

  /* The sequence number heads its own stacked block. */
  .tablewrap tbody th[scope="row"].num {
    font-family: var(--mono);
    color: var(--muted);
    font-size: 0.8125rem;
    margin-bottom: 0.15rem;
  }

  /* A caption is the one place left to explain the table, so give it room.
     `display: block` on the table collapses a caption to its shrink-to-fit width,
     which renders the sentence one word per line. It needs an explicit width. */
  .tablewrap caption {
    display: block;
    width: 100%;
    text-align: left;
    margin-bottom: var(--s3);
  }

  /* A cell with a sub-line (the deposit note, a paid-at stamp) would otherwise
     put that note on the same line as the label. Give it its own row. */
  .tablewrap tbody td[data-label] { flex-wrap: wrap; }
  .tablewrap tbody td[data-label] .sub {
    flex: 1 0 100%;
    text-align: right;
  }
}

/* ---------------------------------------------------------------------------
   Choosing a plan
   ---------------------------------------------------------------------------
   One card per payment count, stacked on a phone and side by side once there is
   room. The figures are monospaced by .pairs already; what these rules add is
   that the plans read as separable objects rather than one long table, because
   the family is being asked to pick one. */

.railset { margin-bottom: var(--s4); }
.railopt {
  display: flex;
  gap: var(--s3);
  align-items: flex-start;
  padding: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
}
.railopt + .railopt { margin-top: var(--s2); }
.railopt input { margin-top: 0.2rem; flex: none; }
.railopt strong { display: block; }
.railopt .sub { display: block; font-size: 0.8125rem; color: var(--muted); }
/* Keyboard focus has to land somewhere visible: the radio itself is small and the
   label is the target most people click. */
.railopt:focus-within { border-color: var(--gold); box-shadow: 0 0 0 3px var(--ring-wash); }

.plans { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); }
@media (min-width: 46rem) {
  .plans { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
}
.planopt {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s4);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}
.planopt .who strong { display: block; font-size: 1.0625rem; }
.planopt .who .fine { display: block; }

/* The MAX card (D-276). Marked, not decorated: the gold hairline is the same
   width as every other card's border so the row of five stays even, and the
   badge is the only thing that changes weight. A filled or shadowed card here
   would read as the recommended plan, which it is not — it is simply the longest
   one the calendar allows, and the longest plan is not advice. */
.planopt-max { border-color: var(--gold); }
.planopt .who .tag {
  display: inline-block;
  margin: var(--s1) 0 var(--s2);
  padding: 0.125rem 0.4375rem;
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  /* Gold on paper, not gold on gold: --gold against --paper is legible, and the
     text is small enough that a tinted fill would cost contrast for nothing. */
  color: var(--gold);
  white-space: nowrap;
}
/* Single column inside a narrow card: two columns of label-over-figure at 15rem
   wraps every label onto three lines. */
.planopt .pairs { grid-template-columns: 1fr; gap: var(--s2); }
.planopt .actions { margin-top: auto; }
/* Buttons here are `display: inline-block`, so the centring is text-align, not
   justify-content. */
.planopt .actions button, .planopt .actions .btn { width: 100%; text-align: center; }

/* An Affirm decline screenshot on the review page. Constrained on purpose: these
   are phone photographs of a phone screen, and an unbounded one pushes the
   review controls below the fold so the reviewer scrolls past what they came to
   do. Full size stays one click away. */
.shot { margin: var(--s3) 0; }
.shot img {
  display: block;
  max-width: 100%;
  max-height: 22rem;
  border: 1px solid var(--line);
  background: var(--paper);
}
.shot figcaption { margin-top: var(--s1); font-size: 0.875rem; }

/* ------------------------------------------------- amount and method chips */
/* The "Make a payment" card. Buttons rather than a <select> because there are
   three of them and they are the three amounts a family actually types; a select
   hides two of the three behind a tap. The selected one is filled, not merely
   outlined: at a glance a family has to be able to see which amount is about to
   be sent. */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: 0 0 var(--s4);
}
.chip {
  font: inherit;
  font-size: 0.9375rem;
  padding: var(--s2) var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.chip:hover { border-color: var(--gold); color: var(--ink); }
.chip:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
/* The selected amount is filled, not merely outlined: at a glance a family has
   to be able to see which amount is about to be sent. */
.chip-on, .chip-on:hover {
  background: var(--btn-bg);
  border-color: var(--btn-bg);
  color: var(--btn-fg);
  font-weight: 600;
}

/* The dollar sign belongs to the field, not to the label above it. */
.money-input {
  display: flex;
  align-items: center;
  gap: var(--s2);
  max-width: 12rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0 var(--s3);
  background: var(--surface);
}
.money-input:focus-within { outline: 2px solid var(--gold); outline-offset: 1px; }
.money-input span { color: var(--muted); }
.money-input input {
  border: 0;
  outline: 0;
  padding: var(--s2) 0;
  font: inherit;
  font-size: 1.0625rem;
  width: 100%;
  background: transparent;
  color: var(--ink);
}

/* A standalone caption over a chip row, matching a .field label. */
p.label {
  font-size: 0.875rem;
  color: var(--ink);
  margin: 0 0 var(--s1);
}

/* Resolving an escalation from the queue list. The field and the button sit on one
 * line on a desktop and stack on a phone, and the field is allowed to shrink so a
 * long student name never pushes the button off the card. */
.resolverow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.resolverow .notefield { flex: 1 1 14rem; min-width: 0; font-size: 0.9375rem; padding: 0.45rem 0.55rem; }
button.small, .btn.small { font-size: 0.875rem; padding: 0.45rem 0.8rem; flex: 0 0 auto; }
.resolverow .fine { flex: 1 1 100%; }
@media (max-width: 520px) {
  .resolverow .notefield, .resolverow button { flex: 1 1 100%; }
}

/* ---------------------------------------------------------------------------
   House style refinements
   ---------------------------------------------------------------------------
   Everything above is the original component set, now reading from the theme
   tokens. This last section is the house look on top of it: Baskerville
   headings, the gold accent on a card head, table zebra, and the dark staff
   shell. Kept at the end, and additive, so the components above stay easy to
   read against the original file. */

/* --------------------------------------------------------------- typography */

h1, h2, h3 { font-weight: 700; letter-spacing: -0.005em; }
h1 { font-size: 1.625rem; line-height: 1.25; }
h2 { font-size: 1.1875rem; }
h3 { font-size: 1.0625rem; }
/* Baskerville sets small and dark; the lede is the one place the serif would
   compete with the heading above it, so it stays in the sans. */
.lede { font-size: 1.0625rem; color: var(--ink-soft); }
.eyebrow { color: var(--gold-ink); font-weight: 600; letter-spacing: 0.1em; }
body.staff .eyebrow { color: var(--gold); }
a { color: var(--gold-ink); text-decoration-thickness: 1px; }
a:hover, a:focus-visible { color: var(--ink); }
/* `:not(.btn)` because this selector is more specific than `.btn` and was
   painting gold text on a gold button — an anchor styled as a primary button
   was unreadable on every staff page. */
body.staff a:not(.btn) { color: var(--gold-ink); }
body.staff a:not(.btn):hover, body.staff a:not(.btn):focus-visible { color: var(--text); }
/* Footer and fine print stay in the sans and out of the way. */
footer.pagefoot { font-family: var(--sans); }
:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

/* -------------------------------------------------------------- masthead */

.masthead {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  box-shadow: inset 0 -1px 0 0 var(--gold);
}
.masthead .brand-sub {
  color: var(--gold-ink);
  font-weight: 600;
  letter-spacing: 0.14em;
}
body.staff .masthead { background: var(--surface); }
body.staff .masthead .brand-sub { color: var(--gold); }
/* The staff pages carry the reversed wordmark (/assets/msg-logo-reversed.png),
   so the mark sits directly on the obsidian masthead with no plate behind it.
   The public pages keep the dark-artwork /assets/msg-logo.png on ivory. */


/* The one-row `.staffnav` under the wordmark used to live here. Replaced by the
   `.console-side` rail, which every staff page now carries; the rules were left
   selecting nothing, so they are gone rather than sitting in a served file. */

/* ------------------------------------------------------------------- cards */

.card {
  background: var(--surface);
  border-color: var(--line);
  padding: var(--s5);
}
/* A hairline of gold across the head of a card is the whole decoration budget:
   it groups the heading with its figures without adding a filled bar that would
   compete with the status pills inside. */
.card-head {
  border-bottom: 1px solid var(--line-soft);
  padding-bottom: var(--s3);
  position: relative;
}
.card-head::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 2.5rem;
  border-bottom: 2px solid var(--gold);
}
.card-head h1, .card-head h2, .card-head h3 { color: var(--ink); }
.figure { color: var(--ink); letter-spacing: -0.01em; }
.queue-total .figure { color: var(--gold-ink); }
body.staff .queue-total .figure { color: var(--gold); }

/* ------------------------------------------------------------------ tables */

thead th { color: var(--ink); background: transparent; }
tbody tr:nth-child(even) td, tbody tr:nth-child(even) th { background: var(--surface-2); }
tbody tr:hover td, tbody tr:hover th { background: var(--gold-wash); }
th, td { border-bottom-color: var(--line-soft); }
@media (max-width: 34rem) {
  /* Rows are stacked blocks here, so the zebra and hover fills would read as
     random shading. Off. */
  .tablewrap tbody tr:nth-child(even) td,
  .tablewrap tbody tr:nth-child(even) th,
  .tablewrap tbody tr:hover td,
  .tablewrap tbody tr:hover th { background: transparent; }
}

/* ------------------------------------------------------------------- forms */

.field label, .fieldset-legend, p.label, legend { font-weight: 600; letter-spacing: 0.01em; }
/* `search` is here because a search box that is not styled is a raw browser
   control on a dark page: white, undersized and nothing like the rest. */
input[type="email"], input[type="text"], input[type="number"],
input[type="date"], input[type="search"], textarea, select {
  border-color: var(--line);
  background: var(--surface);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
input[type="email"]:hover, input[type="text"]:hover, input[type="number"]:hover,
input[type="date"]:hover, input[type="search"]:hover, textarea:hover, select:hover { border-color: var(--muted); }
input[type="email"]:focus-visible, input[type="text"]:focus-visible,
input[type="number"]:focus-visible, input[type="date"]:focus-visible, input[type="search"]:focus-visible,
textarea:focus-visible, select:focus-visible {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px var(--ring-wash);
}
.money-input { background: var(--surface); border-color: var(--line); }
.money-input:focus-within {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px var(--ring-wash);
}
.choice input, .check input, .railopt input { accent-color: var(--gold); }
.choice:has(input:checked) {
  border-color: var(--gold);
  box-shadow: inset 0 0 0 1px var(--gold);
  background: var(--gold-wash);
}
.railopt:has(input:checked) { border-color: var(--gold); background: var(--gold-wash); }
fieldset { border-color: var(--line); background: var(--surface); }
input[type="file"] { color: var(--text); }
input[type="file"]::file-selector-button {
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--ink);
  font-weight: 600;
}
input[type="file"]::file-selector-button:hover { border-color: var(--gold); }

/* ----------------------------------------------------- notices and status */

.notice { background: var(--surface); border-color: var(--line); }
.status { background: var(--surface-2); border-color: var(--line); }
.status-paid, .status-settled,
.status-pending, .status-provisional,
.status-overdue, .status-failed, .status-urgent,
.status-active, .status-on-hold, .status-past-due { background: var(--surface); }
.empty { border-color: var(--line); background: var(--surface-2); }
.docframe { background: var(--surface); border-color: var(--line); }
.skiplink { background: var(--surface); border-color: var(--gold); color: var(--ink); }
.shot img { background: var(--surface-2); border-color: var(--line); }
.planopt { background: var(--surface); }
.planopt-max { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold); }
.planopt .who .tag { color: var(--gold-ink); border-color: var(--gold); }
body.staff .planopt .who .tag { color: var(--gold); }
.chip { background: var(--surface); color: var(--ink); border-color: var(--line); }

/* -------------------------------------------------------------- staff shell */

/* Obsidian page, raised graphite cards. The staff pages also render inside a
   400px HubSpot card, where this reads as a dark panel — deliberate, so a
   console surface never looks like a family-facing one. */
body.staff { background: var(--paper); }
body.staff .card { background: var(--surface); }
body.staff .masthead-inner { padding: var(--s3) var(--s4); }
body.staff h1 { font-size: 1.25rem; }
body.staff h2 { font-size: 1.0625rem; }

/* ==================================================================== console

   The staff dashboard shell, and only that page. Every other staff page keeps
   the single-column document layout it already had: `body.console` is the opt
   in, so this block cannot change a page that is working.

   A sidebar rather than the three-link masthead nav because the dashboard is a
   place somebody sits, not a page they read once. The links are the four pages
   that exist. Nothing here links to a screen we have not built. */

/* 78rem, not 84: minus the 14rem rail that leaves a 64rem content track, which
   is close enough to the 60rem measure the staff pages already used that adding
   the rail does not visibly reflow the queue, the declines list or the forecast
   tables. The rail is bought with page margin, not with the reading width. */
body.console { --column: 78rem; }
/* Stated at this specificity on purpose: `body.staff main` also matches, and
   whichever of the two is more specific wins silently. */
body.console main.console-main { padding: var(--s5); }
/* The pages that predate the shell keep their own `body.staff main` padding and
   their whole inner markup; they gain a rail and nothing else. */
/* margin:0 is the load-bearing part. The shared `main { margin: 0 auto }` turns a
   grid item into a shrink-to-fit box centred in its track, so the queue rendered
   as a 550px column floating in the middle of a 1024px track with a dead gap
   beside the rail. Inside the shell the track is the column. */
.consolegrid > main { margin: 0; min-width: 0; }

/* Named `.consolegrid`, not `.console`: the body carries `class="staff console"`
   for its own token and print rules, and a bare `.console` selector matched the
   body too — the shell grid landed on <body> and the masthead ended up inside
   the sidebar column. */
.consolegrid {
  display: grid;
  grid-template-columns: 14rem minmax(0, 1fr);
  gap: 0;
  max-width: var(--column);
  margin: 0 auto;
  align-items: start;
}
.console-side {
  border-right: 1px solid var(--line);
  padding: var(--s5) var(--s4);
  position: sticky;
  top: 0;
  align-self: stretch;
}
.console-side .eyebrow { margin-bottom: var(--s3); }
.console-side nav { display: grid; gap: 2px; }
.console-side nav a {
  display: block;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  text-decoration: none;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 0.9375rem;
}
.console-side nav a:hover { background: var(--surface-2); color: var(--ink); }
/* The current page is named by its own styling and by aria-current, not by
   colour alone: the gold rule on the left is a second, non-colour signal. */
.console-side nav a[aria-current="page"] {
  background: var(--gold-wash);
  border-color: var(--line);
  box-shadow: inset 3px 0 0 var(--gold);
  font-weight: 600;
}
.console-main { padding: var(--s5); min-width: 0; }

.console-head {
  display: flex;
  gap: var(--s4);
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  margin-bottom: var(--s5);
}
.console-head h1 { margin: 0; }
/* Side by side, and staying side by side on a phone: two full-width stacked
   buttons read as the page's primary actions, which they are not. */
.console-head .actions { display: flex; gap: var(--s2); flex-wrap: wrap; margin: 0; }
/* width:auto is the part that matters: the shared rule at the 30rem breakpoint
   sets `.actions button { width: 100% }`, and flex alone does not undo it. */
.console-head .actions button { flex: 0 0 auto; width: auto; }
.console-head .lede { margin: 0.25rem 0 0; }

/* The number row. Four to a row on a desktop, two on a tablet, one on a phone,
   and never squeezed: a figure that wraps mid-number is unreadable. */
.kpis {
  display: grid;
  /* 10rem, not 11: with the console-main padding a 390px screen has 342px of
     room, and an 11rem floor needed 368 — the row collapsed to one card per line
     and the four numbers stopped being one glance. */
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--s4);
  margin: 0 0 var(--s5);
}
.kpi {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s4);
  min-width: 0;
}
.kpi-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s2);
}
.kpi-label {
  margin: 0;
  font-family: var(--sans);
  font-size: 0.875rem;
  color: var(--ink-soft);
}
.kpi .figure { display: block; font-size: 2.25rem; line-height: 1.1; margin: var(--s2) 0 0; }
/* A dollar total is four times the width of a count. At 2.25rem it leaves the
   card on a phone, so the money cards step down instead of wrapping mid-number. */
.kpi-wide .figure { font-size: 1.5rem; }

/* The roster. Ten columns of a family's account do not fit the console column at
   1440, and a table that is allowed to shrink its first column shreds a long
   legal name one word per line and triples the row height. So the two name
   columns get a floor, the reference never breaks, and the table scrolls inside
   its wrapper rather than the page. */
.roster { width: 100%; }
.roster td:first-child, .roster th:first-child { min-width: 11.5rem; }
.roster td:nth-child(2), .roster th:nth-child(2) { min-width: 8.5rem; }
/* `th.money` is nowrap app-wide, which makes "Months left" set a 101px column
   for a one-digit number. In this table a two-word heading may wrap; the cell
   under it still cannot. */
.roster thead th { white-space: normal; }
.roster .ref { white-space: nowrap; font-family: var(--mono); font-size: 0.75rem; }
.roster td { vertical-align: top; }
/* A long address is the one thing in this table with no natural width. It may
   break mid-string rather than push the money columns off the screen. */
.roster td:nth-child(2) .email {
  display: block;
  max-width: 10.5rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.roster .nowrap { white-space: nowrap; }
/* The status cell holds a pill that cannot wrap. Without a floor the table hands
   it 95px, the pill runs past the edge and "past due" reads as "past d". */
.roster th:last-child, .roster td:last-child { min-width: 8rem; }
/* Wide enough to hold a whole reference, which is the longest thing a person
   will paste into it. */
#filterfield input { max-width: 22rem; }
input[type="search"] { -webkit-appearance: none; appearance: none; }
.kpi .fine { margin: var(--s1) 0 0; }
.kpi a { color: inherit; text-decoration: none; }
.kpi a:hover .figure { color: var(--gold-ink); }

/* Dashboard body: the queue beside the status panel, stacked under 60rem. */
.console-cols {
  display: grid;
  grid-template-columns: minmax(0, 1.85fr) minmax(0, 1fr);
  gap: var(--s4);
  align-items: start;
}
.console-cols > * { margin-bottom: 0; }

/* A compact queue line. Deliberately not .itemrow: this is a glance, and the
   detail lives on /staff-case. */
.brief { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
.brief li {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  align-items: baseline;
  justify-content: space-between;
  padding: 0.55rem 0.75rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.brief .lead { display: flex; gap: var(--s3); align-items: baseline; min-width: 0; }
.brief .lead .status { flex: none; }
/* break-word, not anywhere: `anywhere` split "reauthorization" across two lines
   mid-word on a phone even though the whole word fits on a line of its own. */
.brief .lead .name { overflow-wrap: break-word; min-width: 0; }
.brief .when { flex: none; font-size: 0.8125rem; color: var(--muted); white-space: nowrap; }

/* The status panel. One row per thing we genuinely watch. */
.rows { list-style: none; margin: 0; padding: 0; }
.rows li {
  display: flex;
  gap: var(--s3);
  align-items: baseline;
  justify-content: space-between;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line);
}
.rows li:last-child { border-bottom: 0; }
.rows .label { font-family: var(--sans); font-size: 0.9375rem; }
.rows .label .fine { display: block; }

@media (max-width: 60rem) {
  /* minmax(0,1fr), not 1fr: a bare 1fr floors the track at the content's
     min-content width, and one long absolute timestamp in a queue row was
     enough to push the whole document 4px past the viewport. */
  .consolegrid { grid-template-columns: minmax(0, 1fr); }
  .console-side {
    border-right: 0;
    border-bottom: 1px solid var(--line);
    position: static;
  }
  /* min-width:0 on both, or the four max-content columns become the grid's
     min-content width and push the whole document wider than the phone. */
  .console-side { min-width: 0; }
  /* Two by two, not a horizontal scroller. Four items fit, and a strip that
     scrolls sideways hides the last link behind a gesture nobody is told about
     — "Cash forecast" was off the right edge of a 390px screen. */
  .console-side nav {
    min-width: 0;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s1);
  }
  .console-side nav a { padding: 0.55rem 0.6rem; }
  .console-cols { grid-template-columns: minmax(0, 1fr); }
}

@media print {
  .console-side, .console-head .actions { display: none; }
  .consolegrid { grid-template-columns: 1fr; }
  .console-cols { grid-template-columns: 1fr; }
  .kpi, .brief li, .console-main { break-inside: avoid; }
}

/* A refusal printed next to the input that caused it, on the manual enrollment
   form. Coloured rather than merely small, because it appears among the other
   .fine hint text under a field and would otherwise read as more guidance. */
.fielderror { color: var(--overdue); font-weight: 500; margin-top: var(--s1); }
.field [aria-invalid="true"] { border-color: var(--overdue); }
