/* Plain and legible. No framework: the container has no network at runtime, and
   a CDN link is one more thing that can fail in a clinic on hotel wifi. */

:root {
  color-scheme: light dark;
  /* A neutral ramp rather than three greys. The old set had one border
     weight doing every job -- separating a row, outlining a card, and drawing
     an input -- so everything carried the same visual weight and nothing sat
     in front of anything else. */
  --bg: #f4f6f8;
  --surface: #ffffff;
  --surface-sunk: #eef1f5;
  --border: #e4e8ee;
  --border-strong: #cbd3dd;
  --text: #10151c;
  --muted: #5b6675;
  --accent: #2f6b8f;
  --accent-soft: #e8f0f6;
  --accent-text: #ffffff;
  --danger: #8f2f2f;

  /* The role-preview bar, and the only loud surface in the platform. Amber
     rather than red: previewing is a deliberate state, not an error, but it
     has to be impossible to stop noticing. */
  --warn-surface: #7a5510;
  --warn-text: #fff3dc;
  --warn-border: #96690f;
  /* Amber for *text* on the page background. The two above are a surface and
     its edge, and `.state-admitted` used the surface as a text colour, which
     measured 1.39:1 in the dark palette -- unreadable. See the state chips. */
  --warn-ink: #8a5a00;

  /* Elevation, so a card, the masthead and a menu are not all one plane. */
  --lift-1: 0 1px 2px rgb(16 21 28 / 0.05), 0 1px 3px rgb(16 21 28 / 0.06);
  --lift-2: 0 1px 3px rgb(16 21 28 / 0.06), 0 4px 12px rgb(16 21 28 / 0.07);
  --radius: 6px;

  /* The masthead's height, declared rather than measured, because a sticky
     table header has to come to rest exactly below it. Stated here and applied
     to the masthead as a min-height, so the two cannot drift apart -- the
     variable is the source of truth for both. */
  --masthead-height: 3.7rem;

  /* Chart wedges. Ordered so that neighbours in the sequence are far apart in
     hue: a breakdown is sorted largest first, so wedges 1 and 2 sit next to
     each other on the circle and must not be two blues. Nine of them because
     payer mix is currently nine. */
  --chart-1: #2f6b8f;
  --chart-2: #b4693a;
  --chart-3: #4a8a5c;
  --chart-4: #8a5ca0;
  --chart-5: #b8973f;
  --chart-6: #3f7f8f;
  --chart-7: #a34f5e;
  --chart-8: #6b7f3f;
  --chart-9: #7a6a8f;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Lifted off pure black and given a step between planes: a dark interface
       reads as cheap mainly when every surface is the same value. */
    --bg: #14171c;
    --surface: #1c2027;
    --surface-sunk: #23282f;
    --border: #2c323b;
    --border-strong: #3d454f;
    --text: #e7eaef;
    --muted: #98a3b1;
    --accent: #5599bf;
    --accent-soft: #1e2c37;
    --accent-text: #0f151a;
    --danger: #d98b8b;

    /* Already dark ground, so this one lifts rather than deepens -- the light
       value would sit almost level with the page and stop reading as a bar. */
    --warn-surface: #4a3410;
    --warn-text: #ffe8bd;
    --warn-border: #6b4c17;
    --warn-ink: #d9a441;

    --lift-1: 0 1px 2px rgb(0 0 0 / 0.3), 0 1px 3px rgb(0 0 0 / 0.24);
    --lift-2: 0 2px 6px rgb(0 0 0 / 0.35), 0 8px 24px rgb(0 0 0 / 0.3);

    /* Lifted and desaturated: the light palette on a dark surface reads muddy,
       and a wedge has no text on it to rescue it. */
    --chart-1: #6aa9cc;
    --chart-2: #d99a6e;
    --chart-3: #7fc194;
    --chart-4: #b795c9;
    --chart-5: #d9c078;
    --chart-6: #74b3c1;
    --chart-7: #d1929c;
    --chart-8: #a9bd77;
    --chart-9: #ab9dc4;
  }
}

* {
  box-sizing: border-box;
}

/* Checkboxes and radios in the product's colour rather than the browser's.
   One line, and this application is mostly checkboxes: roles, attributes,
   sites, and every compliance form. */
:root {
  accent-color: var(--accent);
}

/* The keyboard ring, stated once. Browsers draw something by default and it is
   different in each of them, sits tight against the control, and disappears
   entirely on anything with a background. :focus-visible rather than :focus so
   a mouse click does not leave a ring behind it. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

::selection {
  background: color-mix(in srgb, var(--accent) 30%, transparent);
}

/* Everything that moves, moves because it was asked to. Somebody who has
   turned animation off in their operating system has usually done it for a
   reason, and none of the motion here carries meaning. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

a {
  color: var(--accent);
}

/* --- application chrome ------------------------------------------------- */

.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-height: var(--masthead-height);
  padding: 0.75rem 1.25rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--lift-1);

  /* Stays put while the page scrolls, so the way home is always one click
     away. The census and the sign-in list run to hundreds of rows, and the
     logo was the thing that scrolled off first.

     It needs its own background -- which it already had -- or rows show
     through it, and a stacking context above the tables it passes over. */
  position: sticky;
  top: 0;
  z-index: 10;
}

.masthead .wordmark {
  font-weight: 600;
  letter-spacing: -0.01em;
}

.masthead .wordmark a {
  color: inherit;
  text-decoration: none;
}

.masthead .site {
  color: var(--muted);
  font-weight: 400;
}

.whoami {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  color: var(--muted);
  font-size: 0.9rem;
}

/* Off-screen until it takes focus, then pinned where the eye already is. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.6rem 1rem;
  background: var(--accent);
  color: var(--accent-text);
  border-radius: 0 0 var(--radius) 0;
  text-decoration: none;
}

.skip:focus {
  left: 0;
}

/* The masthead is three things: who the customer is, what day it is, and who
   is signed in. The date sits between them and takes the slack, which is what
   stops the bar reading as two items marooned at opposite ends of a wide
   screen. */
.nav-toggle {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  /* Stated here rather than borrowed. This used to carry `button.link` as
     well, so when that class went the toggle fell back to the filled accent
     button and became a blue square in the masthead -- a control whose
     appearance depends on a class it does not own is one that changes when
     somebody edits the other one. */
  background: none;
  border: none;
  border-radius: var(--radius);
  color: var(--muted);
  text-decoration: none;
  transition: background-color 120ms ease-out, color 120ms ease-out;
}

.nav-toggle:hover {
  background: var(--surface-sunk);
  color: var(--text);
}

.masthead-date {
  margin: 0 auto 0 0;
  padding-left: 1.25rem;
  color: var(--muted);
  font-size: 0.85rem;
}

.account {
  position: relative;
}

.account summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0.4rem 0.25rem 0.25rem;
  border-radius: 999px;
  cursor: pointer;
  color: var(--muted);
  font-size: 0.9rem;
  list-style: none;
  transition: background-color 120ms ease-out;
}

/* Safari draws its own triangle and ignores list-style. */
.account summary::-webkit-details-marker {
  display: none;
}

.account summary:hover {
  background: var(--surface-sunk);
}

.avatar {
  display: grid;
  place-items: center;
  width: 1.9rem;
  height: 1.9rem;
  flex: none;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.account .chevron {
  transition: transform 140ms ease-out;
}

.account[open] .chevron {
  transform: rotate(180deg);
}

.account-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  min-width: 15rem;
  padding: 0.75rem 1rem 0.85rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--lift-2);
  z-index: 20;
}

.account-who {
  margin: 0 0 0.6rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--border);
  line-height: 1.35;
}

.account-who strong {
  display: block;
  color: var(--text);
}

.account-who span {
  display: block;
  color: var(--muted);
  font-size: 0.82rem;
  overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  /* The name goes before the date does: the avatar still identifies who is
     signed in, and the date is the thing somebody actually reads. */
  .account-name { display: none; }
}

@media (max-width: 460px) {
  .masthead-date { display: none; }
}

/* The navigation sits hard against the left edge and the content takes
   everything else. It used to be a 1100px column centred in the window, which
   is the right measure for reading prose and the wrong one for this
   application: most screens here are tables of ten or more columns, and that
   cap was the entire reason they had to wrap or scroll. Widening the window
   did nothing, which is the part that made it feel broken.

   Prose does not want a 2000px line, so the measure moved from the shell to
   the things that actually read as prose -- see `.measure` below. Tables and
   grids now get the whole window. */
.layout {
  display: grid;
  /* 200px, measured rather than guessed: the longest label is "Group schedule
     builder" at 160px plus 16px of padding, so 176 is the floor and this
     leaves a little room for a longer one. The 20px it gives back goes to the
     table beside it. */
  grid-template-columns: 200px minmax(0, 1fr);
  gap: 1.5rem;
  margin: 1.5rem 0;
  padding: 0 1.25rem 0 1rem;
}

/* Collapsed: the column goes away entirely rather than narrowing to a rail of
   icons. A rail needs an icon per module and there are none; initials or
   guessed glyphs for "Auth reviews" and "Psych tracker" would be worse than
   nothing. The toggle in the masthead is how it comes back. */
[data-nav="collapsed"] .layout {
  grid-template-columns: minmax(0, 1fr);
}

[data-nav="collapsed"] nav {
  display: none;
}

/* Stays put while the content scrolls. `align-self: start` is what stops a
   grid item stretching to the row's full height, which is what makes sticky
   look like it does nothing -- the element is already as tall as its
   container, so there is nothing for it to stick against. */
nav {
  position: sticky;
  top: calc(var(--masthead-height) + 1rem);
  align-self: start;
  /* Its own scrollbar on a short window: thirty modules is taller than a
     laptop screen, and without this the bottom of the list is unreachable
     once the page itself has stopped scrolling. */
  max-height: calc(100vh - var(--masthead-height) - 2rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Room for the scrollbar so it does not sit on top of the labels. */
  padding-right: 0.25rem;
}

@media (max-width: 720px) {
  /* On a phone the menu overlays the page rather than sitting above it in the
     flow (2026-09-05). Stacked, it pushed the whole screen down by the height
     of thirty modules: opening the menu to go somewhere else moved the thing
     you were reading off the bottom of the screen, and closing it moved it
     back. `position: fixed` takes it out of the flow entirely, so the content
     underneath never moves at all.

     Below the masthead in both senses -- it starts under it, and it sits
     under it in the stacking order, so the toggle that opened it is never
     covered by it. */
  nav {
    position: fixed;
    top: var(--masthead-height);
    left: 0;
    z-index: 9;
    width: min(17rem, 82vw);
    max-height: calc(100dvh - var(--masthead-height));
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 1rem 0.75rem 1.5rem 1rem;
    background: var(--surface);
    border-right: 1px solid var(--border);
    box-shadow: var(--lift-2);
  }

  .layout {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The scrim behind the overlay. Rendered by the server from the same cookie
   the sidebar is, so it can never be showing while the menu is not -- and it
   costs nothing on a desktop, where it is simply never displayed.

   It is what makes tapping beside the menu close it, which is the gesture
   anybody expects of a drawer. With the script blocked it is inert rather
   than a trap: the toggle in the masthead still closes the menu. */
.nav-scrim { display: none; }

@media (max-width: 720px) {
  html:not([data-nav="collapsed"]) .nav-scrim {
    display: block;
    position: fixed;
    inset: var(--masthead-height) 0 0 0;
    z-index: 8;
    background: rgb(0 0 0 / 0.4);
  }
}

/* Kept because two templates still ask for it, and because asking for a wide
   layout should never make a page *narrower* than the default. Every layout is
   wide now, so this says so rather than setting a cap of its own. */
.layout-wide {
  max-width: none;
}

/* A readable line length, for the things that are sentences: the paragraph
   under a heading, and a form nobody wants to track across a full-width
   screen. Applied to the element rather than the shell, so a table in the same
   page is free to use the width. */
.lede,
main > p {
  max-width: 68ch;
}

/* A section heading was a label; it is now the control that opens the section,
   so it needs to look pressable without becoming another button in a column of
   links. Same treatment as before plus a chevron and a hover. */
nav h2,
.nav-section > summary {
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  opacity: 0.75;
  margin: 1.5rem 0 0.35rem;
  padding-left: 0.6rem;
}

nav h2:first-child,
.nav-section:first-of-type > summary {
  margin-top: 0;
}

.nav-section > summary {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-left: -0.35rem;
  padding: 0.25rem 0.4rem 0.25rem 0.25rem;
  border-radius: var(--radius);
  cursor: pointer;
  list-style: none;
  transition: color 120ms ease-out, opacity 120ms ease-out;
}

.nav-section > summary::-webkit-details-marker {
  display: none;
}

.nav-section > summary:hover {
  color: var(--text);
  opacity: 1;
}

/* Pointing right when shut, down when open -- the ordinary convention, and
   the only thing on screen saying a closed section has anything in it. */
.nav-section > summary .chevron {
  flex: none;
  transform: rotate(-90deg);
  transition: transform 140ms ease-out;
}

.nav-section[open] > summary .chevron {
  transform: rotate(0deg);
}

nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

nav li a {
  display: block;
  position: relative;
  padding: 0.4rem 0.6rem;
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--muted);
  font-size: 0.925rem;
  transition: background-color 120ms ease-out, color 120ms ease-out;
}

nav li a:hover {
  background: var(--surface);
  color: var(--text);
}

/* The current page was a solid block of accent -- the loudest thing on the
   screen, competing with the content it was pointing at. A tint plus a marker
   down its edge says the same thing at the weight a signpost deserves. */
nav li a[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

nav li a[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: 0;
  top: 20%;
  bottom: 20%;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
}

main h1 {
  font-size: 1.65rem;
  line-height: 1.2;
  letter-spacing: -0.02em;
  /* Was 0.25rem, which set the lede tight enough under it to read as a
     second line of the heading rather than as a sentence about it. */
  margin: 0 0 0.5rem;
}

.lede {
  color: var(--muted);
  margin: 0 0 1.5rem;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* A card lifted very slightly off the page. Kept subtle deliberately: on the
     dark palette a shadow is nearly invisible and the border does the work, so
     this must not be the thing the layout depends on. */
  box-shadow: var(--lift-1);
  padding: 1.1rem 1.35rem;
}

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}

.tile {
  display: block;
  padding: 0.9rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--lift-1);
  text-decoration: none;
  color: var(--text);
  transition: box-shadow 140ms ease-out, border-color 140ms ease-out;
}

.tile:hover {
  border-color: var(--border-strong);
  box-shadow: var(--lift-2);
}

/* The figure in the product's colour. Four identical grey boxes is what a
   dashboard looks like before anybody has decided what matters on it. */
.tile strong {
  color: var(--accent);
}

/* The number is the point of the tile and was set at body size, so "40 on the
   active census" read as a sentence rather than as a figure. A statistic that
   is the same size as the words around it is not being presented, it is being
   mentioned. */
.tile strong {
  display: block;
  margin-bottom: 0.15rem;
  font-size: 1.75rem;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.tile span {
  color: var(--muted);
  font-size: 0.87rem;
}

.badge {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  font-size: 0.72rem;
  vertical-align: middle;
}

.empty {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 1.5rem;
  color: var(--muted);
  text-align: center;
}

/* --- forms and buttons --------------------------------------------------- */

button,
.button {
  font: inherit;
  /* A label may wrap between words and never inside one. `overflow-wrap:
     anywhere` is set on every ordinary table cell -- it is what stops one long
     surname widening a table -- and a control inside one inherits it, which
     turned "File" into "Fi / le" on a tablet. The same fault `td.shrink`
     already records, arriving by a different route. */
  overflow-wrap: normal;
  word-break: normal;
  padding: 0.45rem 0.9rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--accent-text);
  cursor: pointer;
  transition: background-color 120ms ease-out, transform 60ms ease-out;
}

button:hover,
.button:hover {
  background: color-mix(in srgb, var(--accent) 85%, var(--text));
}

/* A press that shows it landed. Half the reason a form feels unresponsive is
   that nothing acknowledges the click before the page reloads. */
button:active,
.button:active {
  transform: translateY(1px);
}

button[disabled],
button[disabled]:hover {
  background: var(--border);
  color: var(--muted);
  cursor: not-allowed;
  transform: none;
}

/* Declared after `button:hover` and tying it on specificity, so it wins and
   the shared hover never reaches it. Without its own, the delete button would
   be the only thing on the page that does not respond to the pointer, which
   reads as it being broken. */
button.danger:hover {
  background: color-mix(in srgb, var(--danger) 85%, var(--text));
}

/* An anchor used as a control. `.button` above is shared with `<button>`;
   these are the parts an anchor needs and an element does not -- a bare
   anchor is inline, underlined, and lays its padding out in a way that
   overlaps the line above. */
a.button {
  display: inline-block;
  text-align: center;
  text-decoration: none;
  line-height: 1.45;
}

/* The quiet variant, and most controls are one. Cancel, Clear, Previous and
   Next are all things you do, but drawing them in the accent colour leaves a
   page with five equally loud buttons and nothing saying which one matters.
   Filled means "this is the point of the screen"; outlined means "this is
   available". */
button.quiet,
.button.quiet {
  background: var(--surface);
  border-color: var(--border-strong);
  color: var(--text);
}

button.quiet:hover,
.button.quiet:hover {
  background: var(--surface-sunk);
  border-color: var(--muted);
}

/* Row-level actions. Thirty rows each holding a full-size button is a wall of
   controls with the data lost behind it, so these keep the shape and drop the
   weight. Never below the touch minimum -- see the coarse-pointer block. */
button.small,
.button.small {
  padding: 0.2rem 0.55rem;
  font-size: 0.85rem;
}

/* A row of controls, which is nearly always a submit and a way out of the
   form. Laid out here rather than with `&middot;` typed between them in each
   template, which is what several did. */
.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 1rem;
}

/* A heading with one control beside it.
 *
 * Its own class rather than `.chart-row` or `.tops`, which lay out cards and
 * would be two layouts under one name -- the call `.form-list` and `.tickbox`
 * both record after `.pick-list` and `.breakdown` were borrowed and quietly
 * moved somebody else's page.
 *
 * `wrap` and `space-between`. **Measured rather than asserted**: at 375 a
 * short title like "Schedule" and one button sit abreast and fit -- 16+112
 * and 213-355 inside 375, with nothing scrolling -- so the wrap is headroom
 * for a longer heading and not what this page does. The first version of this
 * comment claimed the control drops under the title on a phone, which it does
 * not; a comment nobody has checked is one somebody later preserves at a cost,
 * the call the Tufts ordering rule records.
 *
 * The nested `.actions` loses its top margin because the flex row already
 * does the spacing, and gets it back from `.actions` itself once wrapped. */
.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1rem;
}

.page-head > h1 {
  margin-bottom: 0;
}

.page-head > .actions {
  margin-top: 0;
}

label {
  display: block;
  font-weight: 500;
  margin-bottom: 0.2rem;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="date"],
input[type="time"],
input[type="number"],
/* Added 2026-09-25. It was missing, so every search box on the platform --
   the referral log's, the partner round's, and the alumnus search -- fell back
   to the browser's own control: 147px wide, its own border, at a different
   weight from the select sitting next to it. The same rule `.filters select`
   records, which is that a control restated somewhere else is a control that
   will drift. */
input[type="search"],
select,
textarea {
  font: inherit;
  width: 100%;
  padding: 0.5rem 0.65rem;
  /* The stronger border weight: an input is a thing you can act on and should
     not be drawn at the same weight as the line between two table rows. */
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  transition: border-color 120ms ease-out, box-shadow 120ms ease-out;
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
}

/* A field the pointer is over reads as reachable. */
input:hover:not(:disabled),
select:hover:not(:disabled),
textarea:hover:not(:disabled) {
  border-color: var(--muted);
}

.field {
  margin-bottom: 0.9rem;
  /* Inputs are width:100%, so without a cap a full-width layout gives a text
     box a metre wide -- unusable, and it makes a short form look like a
     mistake. A field is prose-shaped: the cap belongs here, not on the shell,
     so a table on the same page still gets the whole window.

     Not applied inside a grid-shaped form: the compliance question rows place
     their fields into named areas and set their own widths. */
  max-width: 34rem;
}

.question-row .field {
  max-width: none;
}

.errors {
  border-left: 3px solid var(--danger);
  color: var(--danger);
  padding: 0.4rem 0.75rem;
  margin-bottom: 1rem;
}

/* --- sign in ------------------------------------------------------------- */

.signin {
  max-width: 340px;
  margin: 12vh auto;
  padding: 0 1.25rem;
}

.signin h1 {
  font-size: 1.2rem;
  margin: 0 0 0.25rem;
}

.signin p.lede {
  margin-bottom: 1.25rem;
}

/* Google's button, to Google's own guidance: white ground, their mark at its
   given colours, and the wording unaltered. A recoloured mark or a reworded
   label is the version people hesitate over, which is the opposite of what a
   familiar button is for. */
.button-google {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.6rem 1rem;
  background: #fff;
  color: #1f1f1f;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font-size: 0.95rem;
  font-weight: 500;
  text-decoration: none;
}

.button-google:hover {
  background: #f7f8f8;
  box-shadow: var(--lift-1);
}

/* A rule with the word sitting in a gap in it. The gap is painted rather than
   cut, so the label needs the page's own background behind it. */
.signin-or {
  display: flex;
  align-items: center;
  margin: 1.1rem 0;
  color: var(--muted);
  font-size: 0.85rem;
}

.signin-or::before,
.signin-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.signin-or span {
  padding: 0 0.75rem;
}

/* With Google above it, the password form is the second route rather than the
   first, and a second filled button beside a primary one reads as two equal
   choices. */
.signin button.secondary {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border-strong);
}

.signin button.secondary:hover {
  background: var(--surface-sunk);
}

.signin .fineprint {
  margin-top: 1.25rem;
  color: var(--muted);
  font-size: 0.8rem;
}

footer.fineprint {
  margin: 2rem 0;
  padding: 0 1.25rem 0 1rem;
  color: var(--muted);
  font-size: 0.82rem;
}

/* Sits above the masthead and travels with the reader. Loud on purpose: this
   is a state you must not be able to forget you are in, since everything it
   does looks exactly like the platform being broken. */
.preview-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.6rem 1rem;
  background: var(--warn-surface);
  color: var(--warn-text);
  border-bottom: 1px solid var(--warn-border);
  font-size: 0.9rem;
}

.preview-bar p {
  margin: 0;
  max-width: none;
}

.preview-bar button {
  padding: 0.35rem 0.8rem;
  font-size: 0.85rem;
  background: transparent;
  color: inherit;
  border: 1px solid currentColor;
  white-space: nowrap;
}

.preview-bar button:hover {
  background: rgba(255, 255, 255, 0.12);
}

/* So a control can sit inline in a table cell beside ordinary links. */
form.inline {
  display: inline;
}

/* --- the clinical dashboard ---------------------------------------------- */

/* A row of small facts above the cards. Wraps rather than scrolling, because
   the seventh one matters as much as the first. */
.pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}

.pill {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.5rem 0.85rem;
  font-size: 0.78rem;
  color: var(--muted);
}

.pill strong {
  display: block;
  font-size: 1.05rem;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.pill-alert strong { color: var(--danger); }

/* auto-fill rather than a fixed count: nine clinicians on a wide screen should
   not be three columns of three with a gap where a fourth would fit. */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
  gap: 1.25rem;
  align-items: start;
}

.clinician-card h2 { font-size: 0.92rem; margin-bottom: 0.75rem; }

/* Only overdue discharges colour the whole card. Everything else is ordinary
   work with a number against it; a date that has passed means a record is
   wrong now. */
.card-alert { border-color: var(--danger); }

.rule {
  border: none;
  border-top: 1px solid var(--border);
  margin: 0.85rem 0;
}

/* The notice above the grid. A card in normal flow, so `.card + .card` does
   not reach it -- it needs its own gap or the grid starts flush against it. */
.notice {
  margin-bottom: 1.25rem;
}

/* The one ratio on the card, and the only figure that can be empty rather than
   zero. Given its own size so it reads as the headline the rows hang under. */
.sessions {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0;
}

.sessions strong {
  font-size: 1.5rem;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

.sessions span {
  font-size: 0.8rem;
  color: var(--muted);
}

/* Nothing booked is not nought out of nought. */
.sessions-none strong { color: var(--muted); }

/* A count that shows its names on hover. Every card is then the same height
   whatever the numbers are, which is what makes a grid of nine readable at a
   glance. */
.reveal {
  position: relative;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.22rem 0;
  border-radius: var(--radius);
}

.reveal-label {
  font-size: 0.8rem;
  color: var(--muted);
}

.reveal-count {
  font-size: 1rem;
  font-variant-numeric: tabular-nums;
}

.reveal-count.bad { color: var(--danger); }
.reveal-count.warn { color: #b7590a; }
.reveal-count.quiet { color: var(--muted); }
.reveal-count.good { color: var(--muted); }

.reveal-label.bad { color: var(--danger); }
.reveal-label.warn { color: #b7590a; }

/* Nothing to reveal, so nothing responds to the pointer -- a row that
   highlights and then shows an empty box reads as broken. */
.reveal-empty { cursor: default; }

.reveal:not(.reveal-empty):hover,
.reveal:not(.reveal-empty):focus-visible {
  background: var(--surface-sunk);
  outline: none;
}

.reveal-list {
  display: none;
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  z-index: 20;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--lift-2);
  padding: 0.5rem 0.65rem;
  /* A long list scrolls inside the tooltip rather than running off the card. */
  max-height: 14rem;
  overflow-y: auto;
}

/* Focus as well as hover: a keyboard user tabs to the row and the names are in
   the DOM, so a screen reader reads them too. A title attribute would give
   neither. */
.reveal:hover .reveal-list,
.reveal:focus .reveal-list,
.reveal:focus-within .reveal-list {
  display: block;
}

.reveal-item {
  font-size: 0.8rem;
  padding: 0.12rem 0;
  color: var(--text);
}

.reveal-item + .reveal-item {
  border-top: 1px solid var(--border);
}

/* --- listings and workflow states ---------------------------------------- */

table.listing {
  width: 100%;
  /* `separate` with a zero gap, not `collapse`: a collapsed table drops the
     border-radius, which is why this used to carry `overflow: hidden` to clip
     its own corners. That clip also disabled `position: sticky` on the header
     -- an ancestor with a non-visible overflow confines it -- so the header
     scrolled away on exactly the tables long enough to need it. */
  border-collapse: separate;
  border-spacing: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* `collapse` merged adjacent borders for free; `separate` does not, so each
   cell draws only its bottom edge and the last row leaves it to the table. */
table.listing tbody tr:last-child th,
table.listing tbody tr:last-child td {
  border-bottom: none;
}

/* A breakdown row: label, proportional bar, count, share. The bar is decoration
   for the number beside it, so it carries no text of its own. */
/* Qualified with table.listing so it outweighs `table.listing th`, which is
   more specific than a bare `.breakdown th` and would otherwise win. */
table.listing.breakdown th {
  font-weight: 400;
  width: 100%;
  /* Payer names run to "Blue Cross Blue Shield of Massachusetts", which is
     wider than a third of the dashboard. Nowrap here pushed it straight out
     through the side of the card. The label is the only thing in this table
     with anything to give, so it is the only thing allowed to wrap. */
  white-space: normal;
  /* These are proper nouns -- payer names and people -- not column headings.
     table.listing uppercases its th, which turns "Aetna Better Health" into
     shouting and "Ó Fearghail" into something worse. */
  text-transform: none;
  font-size: 0.92rem;
  letter-spacing: normal;
}

/* The landing page's panels. Same shape as the chart row: a floor rather than
   an even split, so a panel with a nine-row legend and one with three figures
   are not forced to the same width. */
.panel-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: 1rem;
  margin-bottom: 2rem;
  /* Each cell its own height. Stretched, a panel holding three figures grows
     to match the donut beside it and reads as a card with something missing. */
  align-items: start;
}

/* Two short panels in one cell, one under the other. */
.panel-stack {
  display: grid;
  gap: 1rem;
  align-content: start;
}

.panel-grid .card,
.panel-grid .card + .card {
  margin: 0;
}

/* A panel that wants the row to itself -- the clinical summary, once there is
   one. */
.panel-full {
  grid-column: 1 / -1;
}

/* One card's worth and never more. `.panel-grid` is `auto-fit`, which collapses
   the empty tracks, so a page holding a single panel stretches it the whole
   width of the window -- and a clinician holds few enough permissions to have
   exactly one. 26rem is the clinical dashboard's own card at its widest, so
   somebody's card on their home page and the same card on the dashboard are
   the same object rather than two shapes of one. */
.panel-card {
  max-width: 26rem;
}

/* A stack of personal cards is one column about one person, so the stack
   itself takes the cap rather than each card taking it separately -- otherwise
   the two sit at whatever width their own content asks for and read as
   unrelated. */
.panel-stack:has(.panel-card) {
  max-width: 26rem;
}

.panel-stack .panel-card {
  max-width: none;
}

/* Two or three figures side by side: admissions against discharges, caseload
   against still-to-book. */
/* A row of big numbers with labels under them. Named for the shape rather
   than for the first panel that wanted one -- admissions and discharges were
   the first, and calling it `.movement` meant the second panel either restated
   the same rules or borrowed a class named after somebody else's subject. */
/* The names behind a figure on the BD dashboard's commercial table.

   `position: fixed` and appended to the body, never to the cell. That table
   sits inside `.table-scroll`, which sets `overflow-x: auto` and so computes
   `overflow-y: auto` too -- an absolutely positioned panel inside it is
   clipped at the card's edge, which is the trap the prescreen flags and the
   clinical cards both hit. Fixed, outside the scroll container, cannot be. */
.flow-peek {
  position: fixed;
  z-index: 40;
  max-width: min(28rem, calc(100vw - 2rem));
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--lift-2);
  padding: 0.45rem 0.6rem;
  font-size: 0.85rem;
  line-height: 1.35;
  /* It follows the pointer's target and never takes it: a panel that could be
     hovered would sit between the pointer and the next number. */
  pointer-events: none;
}

/* The same answer, in full, for somebody who opened it rather than passed
   over it. A dialog rather than a panel in the flow: the top layer is not
   clipped by anything, Escape closes it, and focus is handled for us. */
.panel-dialog {
  width: min(52rem, calc(100vw - 2rem));
  max-height: min(80vh, 44rem);
  overflow-y: auto;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--lift-2);
  padding: 1rem 1.15rem;
}

.panel-dialog::backdrop {
  background: rgb(0 0 0 / 0.45);
}

.flow-detail h3 {
  margin: 0 0 0.75rem;
}

.flow-detail h3 .hint {
  display: block;
  font-weight: 400;
}

/* The BD dashboard's to-do list: a sentence, and the thing to do about it.

   A list rather than a table because the count is already in the sentence and
   a column for it squeezed the sentence to one word a line on a phone. The
   row wraps, so the action drops under the text at a narrow width instead of
   taking the space the words need. */
.tasks {
  list-style: none;
  margin: 0;
  padding: 0;
}

.tasks li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--border);
}

.tasks li:last-child { border-bottom: none; }

.task-what {
  /* Enough to keep a sentence on its own line until there is genuinely no
     room, at which point the action wraps under it rather than the words
     breaking up. */
  flex: 1 1 18rem;
}

.task-what .hint {
  display: block;
  margin-top: 0.15rem;
}

.figure-row {
  display: flex;
  gap: 2rem;
  flex-wrap: wrap;
  margin-bottom: 0.75rem;
}

.figure strong {
  display: block;
  font-size: 1.75rem;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--accent);
}

.figure span {
  font-size: 0.85rem;
  color: var(--muted);
}

/* A figure that is a consequence of the others rather than a measurement of
   its own -- the net of admissions and discharges, or a count that is only
   worth reading when it is not zero. */
.figure-quiet strong {
  color: var(--muted);
}

/* A hint inside a table cell is prose, so it breaks at spaces like prose.
   `overflow-wrap: anywhere` on `td` is there to stop one very long surname
   pushing a table wider than its card, and it inherits into anything in the
   cell -- which at 375 turned an explanatory sentence into "Everybod y".
   A name still breaks anywhere; a sentence no longer does. */
.listing td .hint {
  overflow-wrap: normal;
}

/* A list somebody picks one row from, searched rather than scrolled. Bounded
   in height because it holds every alumnus waiting for a payer -- 123 of them
   on the real clinic -- and an unbounded list would push the button that
   submits it off the bottom of the page.

   The label is the target, 44px like every other option a finger lands on
   (the compliance form and the weekly schedule record the reason). */
.pick-list {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  max-height: 22rem;
  overflow-y: auto;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  max-width: 34rem;
}

.pick-list li + li {
  border-top: 1px solid var(--border);
}

.pick-list label {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  min-height: 44px;
  padding: 0.5rem 0.75rem;
  cursor: pointer;
}

.pick-list label:hover {
  background: var(--surface-sunk);
}

.pick-list input[type="radio"],
.pick-list input[type="checkbox"] {
  flex: none;
  /* The label is `align-items: baseline`, which is right for the text and
     drops a box below the line it belongs to. */
  align-self: center;
}

/* The discharge date sits to the right of the name, and gives way first. */
.pick-list .hint {
  margin-left: auto;
  padding-left: 0.5rem;
  white-space: nowrap;
}

/* The signing pack's list of forms.

   **Its own class rather than `.pick-list` above it.** That one is the
   alumnus picker: 123 radio rows capped at 22rem with an inner scrollbar,
   which is right for a long list somebody chooses one of and wrong for three
   forms on a page with nothing else on it. Borrowed, it put the third form
   below the fold of a box a patient has to notice scrolls -- the
   `.table-scroll` trap on a phone -- and handed "Signed -- thank you" the
   right-aligned nowrap meant for a discharge date. Same call as `.tickbox`
   beside `.tick`: one name meaning two layouts is how a change to either
   quietly moves the other. */
.form-list {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
}

.form-list li {
  padding: 0.75rem;
}

.form-list li + li {
  border-top: 1px solid var(--border);
}

.form-list p {
  margin: 0;
}

.form-list .actions {
  margin-top: 0.5rem;
}

/* A control inside a table cell, under the value it undoes. Its own form,
   because clearing one payer is one POST -- so it must not inherit the block
   margin a form gets on a page, or every row in the table grows by it. */
.clear-payer {
  display: inline;
  margin: 0;
}

/* Nothing to do about it unless it is non-zero, and then it is the point. */
.figure-warn strong {
  color: var(--danger);
}

ul.plain {
  list-style: none;
  margin: 0 0 0.75rem;
  padding: 0;
}

ul.plain li {
  padding: 0.15rem 0;
}

/* Three charts across the dashboard, wrapping to two and then one. `1fr` would
   divide the row evenly and give the payer mix -- nine legend rows -- the same
   width as level of care, which has three. auto-fit with a floor lets each card
   take what it needs and the row rewrap rather than squeezing. */
.chart-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: 1rem;
  margin-bottom: 1rem;
}

/* A row whose cards end where their content does.

   The default is to stretch, which is right where two cards hold comparable
   things. It is wrong for the donut beside the to-do list: the chart is fixed
   in height and the list is three items on a good morning, so stretching left
   a card with a hand's depth of nothing under the last row -- the same "card
   with something missing" the dashboard panels record. */
.chart-row.tops { align-items: start; }

/* The stacked-card spacing does not apply inside a grid: `.card + .card` gives
   every card after the first a top margin, which pushed the second and third
   12px lower than the first and read as a wobble rather than a grid.

   Both selectors are needed. `.chart-row .card` alone ties with `.card + .card`
   on specificity and loses on source order, which is the same trap as
   `table.listing th` against `.breakdown th` further up.

   Any container that lays cards out itself belongs in this list. The clinical
   dashboard's `.cards` was added on 2026-08-29 having reproduced the bug
   exactly -- a gap already spaces them, so a margin can only ever misalign the
   first one. */
.chart-row .card,
.chart-row .card + .card,
.cards .card,
.cards .card + .card {
  margin: 0;
}

.chart-card h2 a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dashed var(--border);
}

.chart-card h2 a:hover {
  border-bottom-color: var(--accent);
}

/* A donut and the legend that carries its numbers. Side by side where there is
   room, stacked where there is not: below about 30rem the chart and a nine-row
   legend fight for the same width and both lose. */
/* Donut above, legend under it at the full width of the card. Side by side
   was the first arrangement and it cost twice over: the chart could only be as
   big as half a narrow card, and the legend had so little width that a payer
   name had nowhere to wrap to. Stacked, both problems are the same fix. */
.breakdown-chart {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  /* Capped and centred, so the same block works in a third of the dashboard
     and on a page of its own. Without it the standalone page stretched the
     legend to seventeen hundred pixels and left the count and the share a
     hand's width from the name they belong to. */
  max-width: 36rem;
  margin: 0 auto;
}

/* On its own page there is room, and the chart is the reason to be there.

   Qualified all the way down because `.breakdown-chart .donut` is declared
   further along the file and ties this on specificity -- source order would
   otherwise decide it, and did. Third time in this stylesheet; the others are
   `table.listing th` and `.card + .card`. */
.breakdown-solo .breakdown-chart {
  max-width: 42rem;
}

.breakdown-solo .breakdown-chart .donut {
  width: min(18rem, 70%);
}

/* The detail sits in the hole in the middle, which is what the hole is for. */
.donut-wrap {
  position: relative;
  flex: none;
  width: 100%;
  display: flex;
  justify-content: center;
}

/* The whole, in the hole. Static -- it is the denominator every wedge is a
   part of, and a number that moves is no use as one. */
.donut-total {
  position: absolute;
  /* Inset, so a long figure wraps inside the hole rather than over the ring. */
  inset: 22%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  pointer-events: none;
  line-height: 1.2;
}

.donut-total strong {
  font-size: 1.75rem;
  transition: font-size 120ms ease-out;
}

/* A currency figure is wider than a count. "$2,487.50" at the resting size
   runs out of hole, so a long one is set smaller rather than picking a size
   that suits neither. */
.donut-total[data-long] strong {
  font-size: 1.15rem;
}

.donut-total span {
  font-size: 0.72rem;
  color: var(--muted);
}

.breakdown-side {
  width: 100%;
  min-width: 0;
}

/* The hovered segment. Its height is reserved rather than collapsing, so the
   legend under it does not jump every time the pointer crosses a wedge. */
.donut-detail {
  margin: 0 0 0.75rem;
  min-height: 2.6rem;
  line-height: 1.3;
  text-align: center;
}

/* The figure moved into the donut, so this panel is the label's. Kept in the
   markup because the chart may be absent -- an empty breakdown draws no donut
   and there is then nowhere else for the numbers to go. */
.breakdown-chart:has(.donut) .donut-detail .detail-figure {
  display: none;
}

.donut-detail .detail-figure strong {
  font-size: 1.1rem;
}

.breakdown-chart:has(.donut) .donut-detail[data-showing] .detail-label {
  color: var(--text);
  font-size: 0.95rem;
}

.donut-detail .detail-figure span::before {
  content: " \00b7 ";
  color: var(--muted);
}

.donut-detail .detail-label {
  display: block;
  color: var(--muted);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

/* At rest there is no figure, only the invitation, and it should not shout. */
.donut-detail:not([data-showing]) .detail-figure {
  display: none;
}

.donut-detail:not([data-showing]) .detail-label {
  font-size: 0.8rem;
}

/* Hovering one wedge fades the others rather than highlighting the one, which
   would need a colour that is not already in the palette. */
.breakdown-chart[data-hovering] .wedge {
  opacity: 0.28;
}

/* Two selectors because a wedge that links through is wrapped in an <a>, and
   the marker goes on whichever element carries data-wedge. Matching only the
   circle silently did nothing on exactly the charts that are clickable. */
.breakdown-chart[data-hovering] .wedge[data-active],
.breakdown-chart[data-hovering] [data-active] .wedge {
  opacity: 1;
}

.breakdown-chart .legend tr[data-active] th {
  font-weight: 600;
}

.wedge,
.legend tr {
  transition: opacity 120ms ease-out;
}

.donut a {
  cursor: pointer;
}

.donut a:focus-visible .wedge {
  outline: 2px solid var(--accent);
}

.breakdown-chart .donut {
  /* Capped as well as sized: on a wide screen a card is roomy enough that a
     donut sized purely in relative units becomes a dinner plate. */
  width: min(13rem, 60%);
  height: auto;
  aspect-ratio: 1;
  display: block;
  /* A wedge is drawn as a stroke on a circle whose circumference is exactly
     100, so its dasharray is its percentage. The pattern starts at 3 o'clock,
     which is why every offset is measured back a quarter turn -- see
     business/charts.py. */
  transform: rotate(0deg);
}

.breakdown-chart .legend {
  width: 100%;
}

/* The count and the share sit next to the label, not at the far edge. */
.breakdown-chart .legend td {
  width: 1%;
  padding-left: 1.25rem;
}

.breakdown-chart .legend th {
  width: auto;
}

.wedge {
  fill: none;
  stroke-width: 7;
  /* Between the surface and the wedge, so neighbouring colours never touch and
     a thin wedge still reads as its own thing. */
  stroke: var(--chart-1);
}

/* The legend's colour chip. Square rather than round: at this size a circle
   next to text reads as a bullet point. */
.key {
  display: inline-block;
  width: 0.7rem;
  height: 0.7rem;
  margin-right: 0.5rem;
  border-radius: 2px;
  vertical-align: baseline;
  background: var(--chart-1);
}

.tone-1 { stroke: var(--chart-1); background: var(--chart-1); }
.tone-2 { stroke: var(--chart-2); background: var(--chart-2); }
.tone-3 { stroke: var(--chart-3); background: var(--chart-3); }
.tone-4 { stroke: var(--chart-4); background: var(--chart-4); }
.tone-5 { stroke: var(--chart-5); background: var(--chart-5); }
.tone-6 { stroke: var(--chart-6); background: var(--chart-6); }
.tone-7 { stroke: var(--chart-7); background: var(--chart-7); }
.tone-8 { stroke: var(--chart-8); background: var(--chart-8); }
.tone-9 { stroke: var(--chart-9); background: var(--chart-9); }

/* A `.key` is a span with no text, so it needs its background stated after the
   .tone rules set it -- and the .tone rules set both stroke and background so
   one class can colour a wedge and a chip alike. */

/* --- The patient-facing schedule form ------------------------------------
   One column, larger touch targets, no navigation. Almost everyone reaching it
   is on a phone, having just scanned a code on the wall. */

.public {
  max-width: 34rem;
  margin: 0 auto;
  padding: 1.5rem 1rem 3rem;
}

/* The desk scanner, and nothing else. 34rem is a phone held in one hand, which
   is what every other page here is; this one is a monitor on a desk, and at the
   phone width two QR codes large enough to scan from across it stack and push
   the page into a scroll. A kiosk nobody can scroll is a kiosk where whatever
   fell below the fold does not exist. */
/* 72rem, measured rather than chosen: at 42vh on a 1080p monitor each code is
   454px and its white surround makes the figure 482, so the pair needs 996px
   plus the card's padding. 64rem left them 49px short and they wrapped. */
.public-wide { max-width: 72rem; }

.public-mark {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}

.public-mark strong { font-size: 1rem; }
.public-mark span { color: var(--muted); font-size: 0.87rem; }

.public .lede { font-size: 1.15rem; color: var(--text); margin-bottom: 0.35rem; }

/* Thumb-sized rather than pointer-sized. */
.public input[type="text"],
.public input[type="date"],
.public textarea,
.public select,
.public button {
  font-size: 1rem;
  padding: 0.7rem 0.75rem;
}

.public input[type="radio"] {
  width: 1.15rem;
  height: 1.15rem;
}

.weekline {
  background: var(--bg);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 0.75rem 0.9rem;
  margin-bottom: 1rem;
}

.weekline strong { display: block; }
.weekline span { color: var(--muted); font-size: 0.85rem; }

.day-block {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  margin-bottom: 0.75rem;
  /* A fieldset defaults to min-width: min-content, which lets a textarea inside
     push it wider than the phone it is being filled in on. */
  min-width: 0;
}

/* Widgets carry a cols or size attribute; on a 375px screen that is wider than
   the card holding them. */
.public input,
.public textarea,
.public select {
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
}

.public ul { list-style: none; }

.public label {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.3rem 0;
}

.public input[type="radio"] { width: auto; flex: none; }

/* Hidden only once the script has run, so a patient with JavaScript off still
   sees the box and can still explain an absence. */
.reason-hidden { display: none; }

.day-block legend {
  font-weight: 600;
  padding: 0 0.35rem;
}

/* The sign-in confirmation is the whole interface, read by somebody already
   turning away from the screen, so the name and the time carry it. */
.signed-in-name {
  font-size: clamp(1.6rem, 7vw, 2.4rem);
  font-weight: 600;
  line-height: 1.1;
}

.signed-in-time {
  font-size: clamp(2.2rem, 11vw, 3.4rem);
  font-weight: 600;
  color: var(--accent);
  line-height: 1.1;
  margin: 0.5rem 0 0.25rem;
}

/* A tablet on the wall, read from across a room. Sized against the viewport so
   it fills whatever it is put on, and always on white -- a QR rendered dark on
   a dark page is not a QR, it is a picture of one. */
.display {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  padding: 2rem 1rem;
  text-align: center;
}

.display h1 { font-size: clamp(1.5rem, 4vw, 2.75rem); }

.display-qr {
  background: #fff;
  padding: clamp(0.75rem, 2vw, 1.5rem);
  border-radius: var(--radius);
  line-height: 0;
}

/* The library sizes its SVG in millimetres, which a screen renders at about
   150px however large the screen is. */
.display-qr svg {
  width: min(58vh, 78vw);
  height: auto;
}

.display-week {
  color: var(--muted);
  font-size: clamp(0.95rem, 1.8vw, 1.35rem);
}

/* --- The two wall codes on the desk scanner ------------------------------- */

/* Added 2026-10-05 so somebody at the desk can scan the wall code from the
   screen instead of turning round. Deliberately smaller and quieter than
   `.display`: that one is a tablet across a room showing a single thing, this
   one sits under a scanner input that is the point of the page.

   On white for the reason `.display-qr` already records -- a QR rendered dark
   on a dark page is not a QR, it is a picture of one. */
.desk-codes {
  margin-top: clamp(1.5rem, 4vh, 2.5rem);
  text-align: center;
}

/* Wraps rather than a media query: two of them side by side need about 34rem,
   and below that they stack on their own with no breakpoint to keep in step
   with anything else. */
.desk-codes-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(1rem, 3vw, 2rem);
  margin-top: 0.75rem;
}

.desk-code {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}

.desk-code-qr {
  background: #fff;
  padding: clamp(0.5rem, 1.2vw, 0.9rem);
  border-radius: var(--radius);
  line-height: 0;
}

/* Sized to be scanned off a monitor from across a desk, which is further away
   and at a worse angle than a phone held over a tablet -- so these are much
   larger than the first version's 13rem cap allowed (2026-10-05, measured on
   the screen the clinic actually uses).

   Both axes still bound it, and that pair is what stops the page scrolling: a
   kiosk nobody can scroll is a kiosk where whatever fell below the fold does
   not exist. `vh` keeps the two codes and the scanner input on one screen on a
   short monitor, `vw` keeps them side by side rather than overlapping on a
   narrow one. */
.desk-code-qr svg {
  /* **What is left after the form, not a fraction of the screen.** Measured:
     everything above and below these codes -- the mark, the lede, the input,
     the button, the captions and the fineprint -- comes to about 34rem and does
     *not* grow with the viewport. So a `vh` fraction is the wrong unit in both
     directions: 42vh under-filled a 1080p monitor at 454px and overflowed a
     768px one at 323px, where only 241px fitted.

     `calc` instead, so the code is as large as the screen can hold and the page
     never scrolls -- a kiosk nobody can scroll is a kiosk where whatever fell
     below the fold does not exist. The `vw` term keeps the pair side by side
     rather than overlapping on a narrow screen.

     **30rem is the card, not a taste**, and it was measured rather than worked
     out -- the computed version was wrong twice. `.public-wide` is 72rem, which
     after the card's padding leaves the row **1075px**; each figure is its code
     plus about 29px of white surround, and the pair needs that doubled plus a
     32px gap. At 31rem the pair came to 1082 and wrapped by **seven pixels**,
     which looks exactly like a size that should have fitted. 30rem leaves 25px
     spare. Wrapping is not cosmetic here: the codes go one below the other and
     the page starts scrolling again. */
  width: min(calc(100vh - 34rem), 38vw, 30rem);
  height: auto;
}

.desk-code-label {
  display: block;
  font-weight: 600;
}

/* --- The printable access code ------------------------------------------- */

.qr-sheet {
  text-align: center;
  padding: 1.5rem;
}

.qr-sheet svg {
  width: 100%;
  max-width: 320px;
  height: auto;
  background: #fff;
  padding: 0.75rem;
  border-radius: var(--radius);
  box-sizing: border-box;
}

.qr-sheet code {
  display: inline-block;
  margin-top: 0.75rem;
  word-break: break-all;
}

@media print {
  .masthead, nav, .filters, .fineprint, .no-print { display: none !important; }
  .qr-sheet svg { max-width: 420px; }
}

/* The timetable grid: a cell is a short stack of names to tick, so the ticks
   want to be compact and the whole week wants to be on one screen. */
.timetable-grid td { vertical-align: top; }

.timetable-grid .tick {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.12rem 0;
  font-size: 0.82rem;
  cursor: pointer;
  white-space: nowrap;
}

.timetable-grid .tick input { margin: 0; flex: none; }
.timetable-grid th a { text-decoration: none; }
.timetable-grid th .hint { margin: 0; }

/* --- The weekly calendar -------------------------------------------------
   A column per day with entries in time order, rather than a time grid. Two
   clinicians genuinely overlap, and a grid has to either lie about that or
   squeeze columns until nothing is readable. */
.week-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 0.75rem;
  align-items: start;
}

@media (max-width: 900px) {
  .week-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 560px) {
  .week-grid { grid-template-columns: minmax(0, 1fr); }
}

.day-column h2 {
  font-size: 0.9rem;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}

.day-column h2 span {
  color: var(--muted);
  font-weight: 400;
  font-size: 0.82rem;
}

.entry {
  border-left: 3px solid var(--border);
  padding: 0.4rem 0 0.4rem 0.6rem;
  margin-bottom: 0.5rem;
  font-size: 0.85rem;
}

.entry strong,
.entry span { display: block; }

.entry strong { font-weight: 600; }

.entry .hint { font-size: 0.78rem; margin: 0; }

/* A group is the fixed structure of the day; a one-to-one is booked around it,
   so the two want telling apart at a glance. */
.entry-group { border-left-color: var(--muted); }
.entry-session { border-left-color: var(--accent); }

/* A table too wide for its card scrolls inside it, rather than squeezing a
   column until names break mid-word or pushing the whole page sideways. For
   grids with a column per weekday, where there is no width left to give. */
.table-scroll {
  overflow-x: auto;
}

/* `anywhere` rather than `break-word`: only the former lets a long token shrink
   the column's min-content width, which is what stops one very long surname
   pushing a table wider than the card holding it. The seed data carries such a
   name precisely so this gets found. */
table.listing td:not(.nowrap) {
  overflow-wrap: anywhere;
}

/* Short values that read badly broken across lines: a level of care, a date.
   Applied per column rather than to the table, because a name or a postal
   address should still wrap.

   An email address belongs here too, though it is not short. `overflow-wrap:
   anywhere` breaks it between arbitrary letters, which leaves an identifier
   nobody can read at a glance or select cleanly -- and the address is the
   thing that column exists to show. A table that refuses to break one needs
   somewhere to put the overflow, so the listing goes inside `.table-scroll`. */
.nowrap { white-space: nowrap; }

table.listing th,
table.listing td {
  text-align: left;
  /* Was 0.5rem/0.75rem. Dense rows read as a spreadsheet export; a little
     room reads as something that was laid out. Costs about one row of the
     forty on screen. */
  padding: 0.65rem 0.85rem;
  border-bottom: 1px solid var(--border);
}

/* The column headings stay while the body scrolls under them. The census runs
   to hundreds of rows and by row eighty there is nothing on screen saying which
   column is the admission date and which is the date of birth -- both are
   dates, and guessing wrong about a patient's admission is not a small error.

   It comes to rest below the masthead, which is itself sticky; both read the
   same variable so they cannot drift apart. The background is required, not
   decoration: without it the rows show through as they pass underneath. */
table.listing thead th {
  position: sticky;
  top: var(--masthead-height);
  z-index: 1;
  /* Distinct from the rows beneath it. The header used to be the same white as
     the body, separated only by a hairline, so a table read as a list with a
     bold first row rather than as a table with a header. */
  background: var(--surface-sunk);
  border-bottom: 1px solid var(--border-strong);
}

/* Not inside a horizontal scroller. `.table-scroll` sets `overflow-x: auto`,
   which makes `overflow-y` compute to `auto` as well, and that turns it into a
   scroll container -- so a sticky header sticks relative to *it* rather than to
   the page, and `top: 59px` pushes the heading 59 pixels down over its own
   first rows. There is nothing to stick to in a box that only scrolls
   sideways, so it does not try. */
.table-scroll table.listing thead th {
  position: static;
}

/* Rounded with the table, since the header is now filled and its square
   corners would otherwise poke out past the card's radius. */
table.listing thead tr:first-child th:first-child {
  border-top-left-radius: var(--radius);
}

table.listing thead tr:first-child th:last-child {
  border-top-right-radius: var(--radius);
}

/* Lining figures of equal width, so a column of counts and dates lines up
   digit under digit instead of drifting. Costs nothing and is most of what
   separates a table that looks typeset from one that looks emitted. */
table.listing,
.tile strong,
.donut-total strong,
.donut-detail {
  font-variant-numeric: tabular-nums;
}

/* Numbers right, so the units line up under each other and a 9 next to a 11
   reads as smaller. Applied per column with a class rather than by guessing
   which cells are numeric: a member id and a phone number are digits and are
   not quantities, and right-aligning those would look like a mistake. */
table.listing th.num,
table.listing td.num {
  text-align: right;
}

/* A row under the pointer, on lists that are read by scanning across: the
   census and the sign-in sheet run to hundreds of rows and losing your line
   halfway across is the ordinary way to misread one. */
table.listing tbody tr:hover td,
table.listing tbody tr:hover th {
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}

table.listing th {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}

table.listing tr:last-child td {
  border-bottom: none;
}

.state {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  font-size: 0.8rem;
  border: 1px solid var(--border);
}

.state-pending_recommendation,
.state-pending_approval,
.state-pending {
  border-color: var(--accent);
  color: var(--accent);
}

/* Where a prescreen has got to with the EMR. "Admitted" is amber rather than
   red: nothing was created and that was the right answer, so it is a state to
   read rather than a problem to fix. */
.state-sent {
  border-color: #2f8f5b;
  color: #2f8f5b;
}

/* Text in `--warn-ink`, never `--warn-surface`. The surface is a background
   colour, and as a text colour it measured 1.39:1 against a dark card -- the
   chip was there and nobody could read it. Found 2026-09-13 by measuring the
   unexcused chip, which had copied this rule. */
.state-admitted {
  border-color: var(--warn-ink);
  color: var(--warn-ink);
}

.state-failed {
  border-color: var(--danger);
  color: var(--danger);
}

/* An unexcused day. Amber rather than red: it is a fact about attendance to
   read and act on, not a fault in the record -- the same call
   `.state-admitted` records about "already admitted". Only the unexcused ones
   are marked, since every absence now carries a category and a chip on all of
   them would be a chip on nothing. */
.state-unexcused {
  border-color: var(--warn-ink);
  color: var(--warn-ink);
}

.state-uploaded {
  border-color: #2f8f5b;
  color: #2f8f5b;
}

/* Waiting for an admission is the ordinary state, not a problem: most
   prescreens are days ahead of the arrival. Grey rather than amber. */
.state-waiting {
  border-color: var(--border-strong);
  color: var(--muted);
}

.state-collected {
  border-color: #2f8f5b;
  color: #2f8f5b;
}

/* A refusal reads as red and a no-show does not, because in this programme a
   refused screen is clinically meaningful and an absence is an absence. */
.state-refused {
  border-color: var(--danger);
  color: var(--danger);
}

.state-not_collected,
.state-cancelled {
  color: var(--muted);
}

/* Transport queue statuses. Requested and being-arranged are both "somebody
   still has to do this", which is what the list is for. */
.state-requested,
.state-in_progress {
  border-color: var(--accent);
  color: var(--accent);
}

.state-active {
  border-color: #2f8f5b;
  color: #2f8f5b;
}

.state-ended,
.state-not_needed {
  color: var(--muted);
}

/* Patient schedule statuses. Late is amber rather than red: they are coming,
   which is a different thing from an absence. */
.state-attending {
  border-color: #2f8f5b;
  color: #2f8f5b;
}

/* Through the token rather than a fixed hex. #d9a441 reads at 7.26:1 on the
   dark card and about 2:1 on a light one, which is why it is the dark
   palette's value and not everybody's. */
.state-planned_late {
  border-color: var(--warn-ink);
  color: var(--warn-ink);
}

.state-planned_absence {
  border-color: var(--danger);
  color: var(--danger);
}

.state-not_scheduled {
  color: var(--muted);
}

.state-approved {
  border-color: #2f8f5b;
  color: #2f8f5b;
}

.state-denied {
  border-color: var(--danger);
  color: var(--danger);
}

.state-withdrawn {
  color: var(--muted);
}

/* The biller's own word on a recovery coaching cycle. Four statuses the
   arithmetic cannot work out, so they are typed and they replace the computed
   state in that column rather than sitting beside it.

   Every one of these reuses a colour already proven as chip *text* in both
   palettes -- the green of `.state-sent`, the accent of `.state-pending`, the
   amber of `--warn-ink`, the grey of `--muted`. Inventing a fifth hue for
   four chips is how `#d99a6e` and a 1.39:1 amber got here, and the label is
   always written out anyway: the colour is what lets the table be scanned,
   never the only thing saying which status it is.

   Read down the column: green has cleared, blue is waiting on somebody else,
   amber is yours to do by another route, grey will never bill. */
.state-good_to_bill {
  border-color: #2f8f5b;
  color: #2f8f5b;
}

.state-auth_pending {
  border-color: var(--accent);
  color: var(--accent);
}

/* Amber for the same reason `.state-admitted` is: a state to read and act on
   rather than a fault. It shares the hue with the computed "Ready to bill",
   which the two can never both occupy -- one chip is drawn, never two. */
.state-internal_billing {
  border-color: var(--warn-ink);
  color: var(--warn-ink);
}

.state-scholarship {
  border-color: var(--border-strong);
  color: var(--muted);
}

/* Padding and the gap under the chip, and nothing else: the border, the
   background and the radius come from the shared control rule, which is what
   `.filters select` records about a restated control being one that drifts. */
.status-pick {
  margin-top: 0.3rem;
  padding: 0.25rem 0.4rem;
  font-size: 0.85rem;
}

/* A marked row draws the dropdown in its status colour and no chip at all:
   the two together said the same four words twice in one cell. The surface
   stays the card's, so these are the same colour-on-surface pairings the
   chips were measured at. */
select.status-pick.state-good_to_bill,
select.status-pick.state-auth_pending,
select.status-pick.state-internal_billing,
select.status-pick.state-scholarship {
  background: var(--surface);
  font-weight: 500;
}

/* **Scholarship takes the ordinary text colour on the control**, where the
   chip takes `--muted`. Grey text on a `<span>` reads as inert, which is what
   that status means; grey text on a control reads as *disabled*, which is a
   different claim and a false one -- it can be changed like any other. Seen
   in a browser against the three coloured ones, where it was the only one
   that looked switched off.

   So three of the four carry a hue and this one does not, which is the design
   rather than a gap: the colour marks the rows with something still to do,
   and a scholarship cycle has nothing. The word is written out either way. */
select.status-pick.state-scholarship {
  color: var(--text);
}

.badge.warn {
  border-color: var(--danger);
  color: var(--danger);
}

.hint {
  color: var(--muted);
  font-size: 0.87rem;
  margin: 0.2rem 0 0;
}

.field-error {
  color: var(--danger);
  font-size: 0.87rem;
  margin: 0.2rem 0 0;
}

button.danger {
  background: var(--danger);
}

/* Was 0.95rem against a 15px body -- a fifth of a pixel larger than the text
   it introduced, which is not a hierarchy. Size alone would have to go a long
   way to separate them, so it takes weight and spacing instead: the eye reads
   the gap under a heading as much as the letters in it. */
.card h2 {
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 0.85rem;
}

.card + .card {
  margin-top: 0.75rem;
}

.card ul {
  margin: 0;
  padding-left: 1.1rem;
}

code.wrap {
  display: block;
  word-break: break-all;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.5rem 0.65rem;
  font-size: 0.85rem;
}

ul.checkboxselectmultiple,
.field ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.field ul li label {
  font-weight: 400;
  display: flex;
  gap: 0.4rem;
  align-items: baseline;
}

.field.grant {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 150px;
  gap: 0.25rem 1rem;
  align-items: baseline;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--border);
  margin-bottom: 0;
}

.field.grant:last-child { border-bottom: none; }

.field.grant label {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  font-weight: 400;
  margin: 0;
}

.field.grant .hint {
  grid-column: 1;
  grid-row: 2;
}

.field.grant select,
.field.grant input[type="checkbox"] {
  grid-column: 2;
  grid-row: 1 / span 2;
  justify-self: end;
}

.field.grant select {
  font: inherit;
  padding: 0.25rem 0.4rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  width: 100%;
}

.filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.75rem;
  align-items: end;
  margin-bottom: 1rem;
}

.filters .field { margin-bottom: 0; }

/* A day and a start time side by side in one table cell. The two controls
   are sized to what they hold rather than `width: 100%` each, which in a
   table cell would stack them and double every row's height. Wraps under
   each other only when the cell really is too narrow. */
.session-pick {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.session-pick select { width: auto; min-width: 11rem; }
.session-pick input[type="time"] { width: auto; }

/* Only the size differs. This used to restate the whole control -- border,
   background, radius -- with the older tokens, so the filter bar quietly drew
   its controls one weight lighter than every other field in the application
   and looked like a different product. Everything but the padding now comes
   from the shared rule above. */
.filters select,
.filters input[type="date"],
.filters input[type="search"] {
  padding: 0.35rem 0.5rem;
}

.state-denied {
  border-color: var(--danger);
  color: var(--danger);
}

/* --- brand marks --------------------------------------------------------- */

.masthead .wordmark a {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  /* The name is one line or nothing. Left to wrap it stacks into three lines on
     a narrow window and shoves the sign-out control off the bar. */
  white-space: nowrap;
}

@media (max-width: 460px) {
  /* No room for both. The mark still says whose this is. */
  .masthead .wordmark span {
    display: none;
  }
}

.masthead .mark {
  height: 28px;
  width: auto;
  display: block;
}

.signin-logo {
  margin: 0 0 1.25rem;
}

.signin-logo img {
  display: block;
  width: 240px;
  max-width: 100%;
  height: auto;
}

/* A checkbox belongs beside its label, not under it. :has() rather than a
   modifier class, so _field.html stays one template for every field type --
   where it is unsupported the layout falls back to the stacked default, which
   is legible, just less tidy. */

.field:has(> input[type="checkbox"]) {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0.55rem;
  align-items: start;
}

.field:has(> input[type="checkbox"]) > label {
  grid-column: 2;
  grid-row: 1;
  font-weight: 400;
  margin: 0;
}

.field:has(> input[type="checkbox"]) > input[type="checkbox"] {
  grid-column: 1;
  grid-row: 1;
  margin-top: 0.25rem;
}

.field:has(> input[type="checkbox"]) > .hint,
.field:has(> input[type="checkbox"]) > .field-error {
  grid-column: 2;
}

/* One question in the form editor. Nested cards need a lighter ground than the
   card they sit in, or the rows disappear into it. */

.card .card.question-row {
  background: var(--bg);
  margin-bottom: 0.75rem;

  /* A question has nine inputs and most of them are short. Stacked, eight
     questions ran to four thousand pixels; paired, they fit on a screen and a
     half. Named areas rather than source order, because the field order that
     reads well in the form class is not the one that lays out well. */
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0 1rem;
  grid-template-areas:
    "label     label     label        type"
    "help_text help_text required     allows_other"
    "choices   choices   prefill      prefill"
    "ORDER     DELETE    DELETE       DELETE";
}

.question-row > .label { grid-area: label; }
.question-row > .type { grid-area: type; }
.question-row > .required { grid-area: required; }
.question-row > .help_text { grid-area: help_text; }
.question-row > .choices { grid-area: choices; }
.question-row > .allows_other { grid-area: allows_other; }
/* The area is `prefill`, not `default`: CSS reserves `default` as an ident, so
   `grid-area: default` is dropped as invalid and the cell auto-places instead
   -- silently, and only visible as a textarea half the width it should be. */
.question-row > .default { grid-area: prefill; }
.question-row > .ORDER { grid-area: ORDER; }
.question-row > .DELETE { grid-area: DELETE; }

/* Inputs carry a size or cols attribute from the widget, which in a grid cell
   leaves them stopping short of the column they were placed in. */
.question-row input[type="text"],
.question-row select,
.question-row textarea {
  width: 100%;
  box-sizing: border-box;
}

/* The two checkboxes sit beside text inputs, so they need the label's height
   above them or they float against the top of their cell. */
.question-row > .required,
.question-row > .allows_other,
.question-row > .DELETE {
  align-self: end;
}

@media (max-width: 800px) {
  .card .card.question-row {
    display: block;
  }
}

/* The notification exactly as the biller sent it, on a review's own page.
   Monospaced and preserved: the whole reason it is kept is so a person can
   compare what the parser made against what actually arrived, and reflowing
   it would defeat that. It wraps rather than scrolling -- a notification is
   three short lines, and a horizontal scrollbar on three short lines reads as
   though something is hidden. */
pre.source {
  margin: 0;
  padding: 0.75rem 1rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Columns whose content is a short fixed vocabulary: a status pill, a couple of
   attribute badges, a single "Open" link. `width: 1%` is the shrink-to-fit
   trick -- a percentage below the column's minimum content width makes the
   browser give it exactly that minimum and hand every remaining pixel to the
   auto-width columns. Without it a table divides its width roughly evenly, so
   a Status column sits half empty while the email address beside it wraps onto
   a second line and doubles the height of every row. */
/* A listing where every column is a short identifier -- a name, an address, a
   role, a status -- and none of them reads well broken across two lines. Rather
   than deciding column by column which one may wrap, nothing wraps and the
   table scrolls inside its card when the window is too narrow.

   Chosen after doing it the other way. Making one column no-wrap hands its
   full width demand to the others, so the wrap moves rather than going away:
   fixing the email address pushed the name and the role onto two lines each.
   The width has to come from somewhere, and a scrollbar is the only place that
   is not another column. Use it only where every column is genuinely short --
   a free-text note or a postal address still belongs in a wrapping cell. */
table.listing.no-wrap th,
table.listing.no-wrap td {
  white-space: nowrap;
  overflow-wrap: normal;
}

table.listing th.shrink,
table.listing td.shrink {
  width: 1%;
  /* `overflow-wrap: anywhere` is set on every ordinary cell so one long surname
     cannot widen the table. Here it is actively harmful: it lets a word break
     between any two letters, which makes the column's minimum content width a
     single character, and `width: 1%` then collapses the column to it. "Open"
     came out as "Op / en" in a 36px column. */
  overflow-wrap: normal;
  /* Two pills in one cell ("Active" and "vendor") stay side by side. Letting
     them stack saves eight pixels of column and costs a whole extra line on
     that row, which is the trade this class exists to avoid. */
  white-space: nowrap;
}

/* A state chip that is also a link -- a day on the patient schedule grid,
   which opens the amend screen. It stays a pill: an underline through a pill
   reads as a rendering fault, and the grid has no width to give a separate
   control. The hover fill is what says it can be pressed. */
a.state {
  text-decoration: none;
}

a.state:hover,
a.state:focus-visible {
  background: var(--surface-sunk);
}

/* A pill is a label, not a paragraph. Wrapping one puts the words in the middle
   of a tall oval with blank space above and below them, which reads as a
   rendering fault rather than a status. They are all one or two short words, so
   there is nothing to lose by refusing. */
.badge,
.state {
  white-space: nowrap;
}

/* A "what needs fixing" note, revealed by findings.js when its check fails.
   `display` is set on the wrapper rather than on the field, because the field
   include is shared and must not learn about compliance. Without the script
   nothing sets [hidden] and every note is simply visible, which is the
   behaviour the form is written to work with. */
.note-for[hidden] {
  display: none;
}

/* A compliance check's answers sit on one line, inside the filing form only.
   A facility walkthrough is forty-six of them; stacked three-deep that is a
   seven-thousand-pixel page for a job with two findings in it, and the paper
   form it replaces has Pass / Fail / N/A side by side. Scoped to the form
   rather than done in `_field.html`, which every screen shares: a radio list
   of six payer names wants to stay stacked. */
.compliance-form .field > div > div {
  display: inline-block;
  margin-right: 1rem;
}

.compliance-form .field > div > div label {
  font-weight: 400;
  white-space: nowrap;
}

/* A compliance check's answers are tapped, not clicked. The walkthrough is
   forty-six checks of three options -- a hundred and thirty-eight taps, made
   while walking round a building holding a phone -- and the label was 23px
   tall, under even the 24px WCAG 2.5.8 minimum and half Apple's 44pt advice.
   A mis-tap on a safety check is a wrong answer on a signed record.

   The whole label is the target, so the text is tappable rather than just the
   dot. Scoped to the filing form: a radio list of six payer names elsewhere is
   read at a desk. */
.compliance-form .field > div > div label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 44px;
  padding: 0 0.55rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}

.compliance-form .field > div > div label:has(input:checked) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

.compliance-form .field > div > div {
  margin-right: 0.4rem;
  margin-bottom: 0.35rem;
}

.compliance-form .field > div > div input[type="radio"] {
  width: 18px;
  height: 18px;
}

/* The weekly schedule is filled in on a phone, essentially always: a patient
   scans the QR on the wall and answers four questions a day for five days.
   The option labels were 32px, under the 44px a thumb wants, and a mis-tap
   here books somebody a session on a day they said they would be away.

   The whole label is the target so the words are tappable, not just the dot. */
.day-block label:has(input[type="radio"]),
.day-block label:has(input[type="checkbox"]) {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  min-height: 44px;
  padding: 0 0.5rem;
  margin: 0.15rem 0;
  border-radius: var(--radius);
  cursor: pointer;
}

.day-block label:has(input:checked) {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.day-block input[type="radio"],
.day-block input[type="checkbox"] {
  width: 18px;
  height: 18px;
  flex: none;
}

/* The generated QR and its caption, sized to be printed and stuck on a wall.
   The code is drawn as an SVG path, so it scales to whatever the paper is
   without going soft. */
.qr-sheet { text-align: center; }

.qr-sheet .qr svg {
  width: 100%;
  max-width: 22rem;
  height: auto;
}

.qr-caption {
  margin: 0.5rem 0 0;
}

.qr-target {
  overflow-wrap: anywhere;
}

/* --- Engagement: outreach ticks ------------------------------------------
   A grid of checkboxes: one per milestone per person, up to eight across.
   Named `tickbox` rather than `tick` because `.timetable-grid .tick` already
   holds that name for a differently shaped control, and one name meaning two
   layouts is how a change to either quietly moves the other. */
.tickbox {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  /* The whole cell, not the dot. This is worked down a long list with a phone
     in the other hand, and a mis-tap here records a call that never happened
     against a former patient. 44px is Apple's advice and comfortably over the
     24px WCAG 2.5.8 minimum, which is the same call the compliance form and
     the weekly schedule already made. */
  min-height: 44px;
  min-width: 44px;
  cursor: pointer;
}

.tickbox.wide {
  justify-content: flex-start;
  white-space: nowrap;
}

.tickbox input {
  margin: 0;
  flex: none;
  width: 1.1rem;
  height: 1.1rem;
}

/* Shaded while the contact is owed, plain once it is done or still ahead. The
   date it falls due is on the box's title either way -- the shading says which
   cells to work today, and the title answers everything else. */
table.outreach td.milestone,
table.outreach th.milestone {
  text-align: center;
  padding: 0 0.25rem;
}

/* One cell in a `no-wrap` table allowed to wrap, because it holds words rather
   than an identifier: "Not an alumnus" on one line is 134px of a table that
   has none to spare, and on two it costs nothing -- the row is already 44px
   tall for the tap targets. */
table.listing.no-wrap td.wrap {
  white-space: normal;
}

table.outreach td.milestone.due {
  /* A fill and nothing else. An inset left border read as a column divider
     rather than a highlight -- a td is as tall as its row, so a 2px edge on
     one cell draws a full-height rule between two columns. */
  background: var(--surface-sunk);
}

/* Column headings here are milestone names, not field names, so the listing
   table's uppercasing turns "180 days" into shouting for no purpose. Same
   family as `table.listing.breakdown th`, and specific enough to outweigh it
   for the same reason a bare `.outreach th` would not be. */
table.listing.outreach th {
  text-transform: none;
  letter-spacing: 0;
}

/* Touch. A finger is not a pointer: 44px is Apple's advice and comfortably
   over the WCAG 2.5.8 minimum of 24px, and it is what the compliance form,
   the weekly schedule and the outreach ticks already use. Keyed to the input
   device rather than to a width, so a tablet gets it in landscape too and a
   narrow desktop window does not get it for nothing. */
@media (pointer: coarse) {
  button,
  .button {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* Small is a visual weight, never a smaller target. */
  button.small,
  .button.small {
    min-height: 44px;
    padding-inline: 0.75rem;
  }

  /* An icon button takes the minimum in both directions, or the shared rule
     leaves it 44px tall and 30px wide. */
  .nav-toggle {
    width: 44px;
    height: 44px;
  }
}

/* --- the embedded clinical calendar --------------------------------------
   Someone else's application in a frame, so the only job here is to give it a
   sensible shape. A fixed height rather than an aspect ratio: a week view is
   a fixed number of hours tall and squashing it to a ratio makes the rows
   unreadable on a narrow window. */
.calendar-frame {
  padding: 0;
  overflow: hidden;
}

.calendar-frame iframe {
  display: block;
  width: 100%;
  height: 70vh;
  min-height: 520px;
  border: 0;
}

/* A date and a button on one line inside a table row. */
.inline-record {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.inline-record input[type="date"] {
  width: auto;
  padding: 0.2rem 0.4rem;
}

/* One researched candidate, on the BD intelligence page. Separated by a rule
   rather than boxed: they sit inside a card already, and a card inside a card
   reads as two levels of importance where there is only one. */
.candidate + .candidate {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}

.candidate h3 {
  margin: 0 0 0.25rem;
  font-size: 1rem;
}

.candidate p {
  margin: 0 0 0.5rem;
}

/* The two decisions sit side by side. `inline` rather than one form with two
   buttons: they post to different URLs, and a single form deciding which by
   button name is a thing to get wrong later. */
form.inline {
  display: inline-block;
  margin-right: 0.5rem;
}

/* --- Referral log: what became of each enquiry ------------------------- */
/* Four families, not thirteen statuses (`bd/statuses.py`). A colour per
   status is a rainbow nobody can hold in their head; the distinctions worth
   seeing on a list being worked are coarser than the vocabulary.

   A stripe down the row, never a wash across it. These rows carry names and
   dates that have to stay readable, and a tinted background either fights the
   text or is too faint to be seen at all. The status is also spelled out in
   its own column, so colour is redundant encoding -- the table reads the same
   printed in grey, with the stylesheet missing, or to somebody who cannot
   tell the two reds apart.

   **`.status-key`, never `.key`.** `.key` is the donut legend's colour chip
   (above, ~1461): a 0.7rem square with a background. Reusing the word turned
   the whole legend paragraph into one tiny coloured square with the swatches
   crammed inside it -- and, because a later rule of equal specificity wins,
   made every legend chip on every donut in the platform `display: flex`. The
   same failure `.tickbox` is named for, by the same route: a word that reads
   as generic is usually already taken. */
/* The colour travels as a custom property, not as a border rule per status.

   The obvious way round has a trap in it, and it shipped: a base rule setting
   the `border-left` *shorthand* -- which includes a colour -- at
   `table.listing tbody tr[...] > td:first-child`, then a colour per status at
   the shorter `tr.status-open > td:first-child`. Different properties to
   read, the same property to the cascade: the shorthand's `transparent` beat
   every longhand that followed it, because the base selector is the more
   specific one. Every row rendered its stripe in transparent and the feature
   looked simply absent.

   A custom property cannot fail that way. `--row-accent` is set on the row
   and inherits to the cell, so the two rules never compete for one property
   and the colour rules need no selector weight at all. A status that sets
   nothing falls through to the fallback and is uncoloured, which is what an
   unrecognised status should be: a row to look at, not one coloured as though
   it were understood. */
table.listing tbody tr[class^="status-"] > td:first-child,
table.listing tbody tr[class*=" status-"] > td:first-child {
  border-left: 3px solid var(--row-accent, transparent);
  /* Compensates so text on a coloured row sits on the same left edge as every
     other table's. `table.listing td` sets 0.85rem. */
  padding-left: calc(0.85rem - 3px);
}
.status-open     { --row-accent: var(--chart-1); }
.status-arrived  { --row-accent: var(--chart-3); }
.status-missed   { --row-accent: var(--chart-5); }
.status-declined { --row-accent: var(--chart-7); }

.status-key {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  align-items: center;
}
.status-key .swatch {
  display: inline-block;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 2px;
  margin-right: 0.3rem;
  vertical-align: -1px;
}
/* The same token the row stripe uses, so a colour cannot be changed in one
   place and not the other -- a key that disagrees with the table it explains
   is worse than no key. */
.status-key .swatch { background: var(--row-accent, var(--border-strong)); }

/* --- Editing a referral in place -------------------------------------- */
/* The cell keeps its width while an input sits in it, so a table does not
   jump about as somebody moves down a column. */
table.listing td[data-field] { cursor: default; }
table.listing td.editing input,
table.listing td.editing select {
  width: 100%;
  min-width: 6rem;
  padding: 0.15rem 0.3rem;
}
table.listing td.saving { opacity: 0.6; }
/* Green for a moment, then gone. Long enough to see which cell took, short
   enough that a column of them is not a christmas tree. */
table.listing td.saved { box-shadow: inset 0 0 0 2px var(--chart-3); }
table.listing td.refused { box-shadow: inset 0 0 0 2px var(--chart-7); }

/* --- A standing caution ------------------------------------------------ */
/* Not `.errors`: that says something went wrong and the reader should fix
   it. This says something is permanently true and worth reading before you
   act -- the PHI warning on the coach, a partner whose contact is overdue.
   Named `.caution` rather than `.notice`, which is already taken (and is only
   spacing), and rather than `notice-alert`, which was used on the partner
   page and never existed at all: the class did nothing and the banner
   rendered as a plain card, which is exactly how a made-up class name fails
   -- silently, and looking almost right. */
.caution {
  border-left: 3px solid var(--chart-5);
  background: var(--surface-sunk);
}

.caution h2 {
  margin-top: 0;
  font-size: 1rem;
}

.caution p:last-child { margin-bottom: 0; }

/* --- BD coach ---------------------------------------------------------- */
/* Shaped like the chat window people already know, because that is what it
   is: your message in a tinted block, the answer as plain prose beneath it,
   and a composer that looks like one box rather than a field with a button
   parked next to it.

   The asymmetry is the whole design. A question is short and already known to
   the person who typed it, so it gets a container and sits back. An answer is
   prose you are about to read closely, so it gets no container at all --
   nothing between the reader and the words, and a measure to read them at. */
.coach {
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--accent);
  /* Its own breathing room. A card among cards can share the page's rhythm;
     this one has a scrolling thread and a composer at its foot, and at the
     default spacing the last line of it sat against the figures underneath
     and read as one run-on block. */
  margin-bottom: 2rem;
}

.coach-thread {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  min-height: 8rem;
  max-height: 60vh;
  overflow-y: auto;
  padding: 0.25rem 0.25rem 0.5rem;
  margin-bottom: 0.75rem;
  scroll-behavior: smooth;
}

.coach-turn { margin: 0; }

/* Yours: a rounded block, inset from the left so the two sides of the
   conversation are told apart by shape before anything is read. */
.coach-turn.from-you {
  align-self: flex-end;
  max-width: 85%;
}

.coach-turn.from-you .said {
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.6rem 0.85rem;
  white-space: pre-wrap;
}

/* The answer: no box, no tint, and a reading measure -- the argument `.lede`
   already makes, since a paragraph run across the width of a table is not
   readable. */
.coach-turn.from-coach .said { max-width: 70ch; }
.coach-turn.from-coach .said > p { margin: 0 0 0.75rem; line-height: 1.6; }
.coach-turn.from-coach .said > p:last-child { margin-bottom: 0; }
.coach-turn.from-coach .said ul,
.coach-turn.from-coach .said ol { margin: 0 0 0.75rem 1.2rem; padding: 0; }
.coach-turn.from-coach .said li { margin-bottom: 0.4rem; line-height: 1.55; }
.coach-turn.from-coach .said a { overflow-wrap: anywhere; }

/* The note under the composer belongs to the composer, not to whatever comes
   next on the page. */
.coach > p.hint:last-child { margin-bottom: 0; }
.coach-turn.from-coach .said strong { color: var(--text); }

.coach-turn.failed .said {
  color: #b7590a;
  max-width: 70ch;
}

.coach-waiting {
  color: var(--muted);
  font-size: 0.9rem;
  margin: 0;
}

/* Standing notes: what the coach has been told, on the page that uses them.
   Set apart from the conversation above it with a rule rather than a card,
   because it is part of the coach rather than a panel of its own -- a second
   card here read as a second feature. */
.coach-notes {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}

.coach-notes h3 {
  margin: 0 0 0.25rem;
  font-size: 1rem;
}

.note-list {
  margin: 0.75rem 0;
  display: grid;
  gap: 0.5rem;
}

/* Each note is its own line: the fact, the control that removes it, then who
   wrote it. A grid rather than floats so the Forget button sits on the same
   baseline however many lines the note runs to. */
.note-list li {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.25rem 0.75rem;
  align-items: start;
  padding: 0.5rem 0.65rem;
  background: var(--surface-sunk);
  border-radius: var(--radius);
}

.note-list li > .hint {
  grid-column: 1 / -1;
  font-size: 0.8rem;
}

.note-list form { margin: 0; }

.note-form label {
  display: block;
  font-size: 0.9rem;
  margin-bottom: 0.25rem;
}

.note-form textarea {
  width: 100%;
  /* Two lines, which is what a standing fact takes. It is not a composer:
     nothing here is sent to the model on its own, so it gets an ordinary
     field rather than the chat box's borderless treatment. */
  resize: vertical;
}

.note-form .actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* The confirmation sits beside its button rather than above it: it is a
   condition of pressing that button, not a separate question. */
.tickbox-inline {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.9rem;
  color: var(--muted);
  margin: 0;
}

@media (pointer: coarse) {
  .tickbox-inline { min-height: 44px; }
}

/* The model's own summary of what it is doing, shown under the status line
   while it searches. A question that needs the web writes nothing for half a
   minute or more, and this is what fills that -- so it is deliberately quiet
   and deliberately capped: a status line, not a transcript, and it is gone
   the moment the answer starts.

   `coach-aside` rather than a shorter word: `.said` is the answer's own body
   and `.hint` is used the width of the platform, and a third meaning on
   either is how a change to one quietly moves the other. */
.coach-aside {
  margin-top: 0.35rem;
  padding-left: 0.6rem;
  border-left: 2px solid var(--border);
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.45;
  /* Long enough to read a sentence of reasoning, short enough that it never
     pushes the composer off a laptop screen. It scrolls itself. */
  max-height: 6.5rem;
  overflow-y: auto;
  /* The summary arrives as prose with its own newlines. */
  white-space: pre-wrap;
  max-width: 70ch;
}

/* One box, the way a composer reads: the field has no border of its own and
   the button sits inside the same outline. A textarea with a button parked
   beside it looks like a form; this looks like somewhere to type. */
.composer {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  padding: 0.5rem 0.5rem 0.5rem 0.75rem;
  background: var(--surface);
}

.composer:focus-within { border-color: var(--accent); }

.composer textarea {
  flex: 1;
  border: none;
  background: none;
  padding: 0.35rem 0;
  resize: none;
  max-height: 12rem;
  overflow-y: auto;
  font: inherit;
  color: inherit;
}

.composer textarea:focus { outline: none; }
.composer .actions { margin: 0; display: flex; gap: 0.35rem; }

/* The confirmation, in the thread rather than a browser dialog: a `confirm()`
   gives nowhere to say *which* name it is asking about, and the question is
   only answerable if you can see that. */
.coach-confirm {
  border-left: 3px solid var(--chart-5);
  background: var(--surface-sunk);
  padding: 0.75rem;
  border-radius: 8px;
  max-width: 70ch;
}
.coach-confirm p { margin-top: 0; }

/* A candidate's reasoning, under its name. Collapsed the row is one line,
   which is what makes a list of six scannable; open it is the paragraph that
   decides whether to drive there. */
details.why > summary { cursor: pointer; }
details.why > p { margin: 0.4rem 0 0; max-width: 60ch; font-weight: normal; }
details.why > p a { overflow-wrap: anywhere; }

/* --- Signing ---------------------------------------------------------- */

/* The pad is a box somebody draws in with a finger as often as a mouse, so it
   is tall enough to sign in and `touch-action: none` keeps a drag from
   scrolling the dialog underneath it. */
.signature-pad canvas {
  display: block;
  width: 100%;
  height: 9rem;
  background: var(--surface-sunk);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  touch-action: none;
  cursor: crosshair;
}

.signature-mark {
  max-width: 16rem;
  max-height: 5rem;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.25rem 0.4rem;
}

.attestation {
  border-left: 3px solid var(--border-strong);
  padding-left: 0.75rem;
}

/* --- The clinical document -------------------------------------------- */

/* A note is a document, not a settings page, and it is read as one -- by the
   clinician writing it, by a supervisor cosigning, and by whoever answers a
   payer's records request two years later. So it keeps the shape the clinic
   already knows from the EMR it replaces: titled panels, the question on the
   left and the answer on the right, and a width that reads like a page rather
   than filling whatever monitor it is opened on. */
.document {
  max-width: 64rem;
}

/* The panel's title sits in a bar across the top of the card, which is what
   makes a run of them read as one document rather than as eight unrelated
   boxes. Negative margins pull it out to the card's own edges; they have to
   match `.card`'s padding, which is why both are written here. */
.doc-section > h2 {
  margin: -1.1rem -1.35rem 1rem;
  padding: 0.6rem 1.35rem;
  /* Tinted rather than grey (2026-10-04, the owner's ask): it is the band the
     clinic reads a panel by in the EMR this replaces. `--accent-soft` is a
     pale blue in the light palette and a deep slate in the dark one, so the
     bar carries the same weight in both -- a saturated blue would be right in
     one and a stripe of neon in the other. */
  background: var(--accent-soft);
  color: var(--accent);
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
  font-size: 0.95rem;
  letter-spacing: 0.01em;
}

/* Label left, control right -- the layout of the paper form and of the EMR.
   `_field.html` is untouched: this re-lays out the markup every form in the
   application already renders, so a field here gains a position without
   gaining a template. */
.document-form .field {
  display: grid;
  grid-template-columns: 15rem minmax(0, 1fr);
  gap: 0.15rem 1.1rem;
  align-items: start;
  max-width: none;
  margin-bottom: 0.75rem;
}

.document-form .field > label {
  grid-column: 1;
  /* The whole height of the field, so the label's first line sits level with
     the prompt above the box rather than a row below it. */
  grid-row: 1 / -1;
  text-align: right;
  padding-top: 0.45rem;
  font-weight: 500;
  /* A six-line ASAM prompt is a label; it wraps rather than squeezing the
     answer box, which is the column that has to stay usable. */
  line-height: 1.35;
}

.document-form .field > :not(label) {
  grid-column: 2;
}

.document-form .field .hint,
.document-form .field .field-error {
  grid-column: 2;
  margin-top: 0.25rem;
}

/* The prompt goes above the box it prompts for. `_field.html` renders it after
   the widget -- right for a one-line explanation under a text input, wrong for
   a six-item list of what a dimension should cover, which is read *before*
   writing and otherwise sits under the box looking like a caption for the next
   question. `order` reorders the grid's auto-placed items without a second
   template. */
.document-form .field .hint {
  order: -1;
  margin: 0 0 0.3rem;
}

/* The service block is one row of short answers -- date, two times, level of
   care, who delivered it -- the way "Service Duration: Started / Ended" runs
   across the top of the clinic's own form. Each keeps its label above it: at
   this width a label to the left of a time box leaves neither room to read. */
.service-block {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0 1rem;
}

.service-block .field {
  display: block;
  margin-bottom: 0.6rem;
}

.service-block .field > label {
  display: block;
  text-align: left;
  padding-top: 0;
  font-size: 0.87rem;
  color: var(--muted);
}

/* A line the chart fills in: printed, never asked. Reads as part of the
   document's header rather than as an answer somebody gave. */
.printed {
  margin: 0.15rem 0;
}

.printed strong {
  display: inline-block;
  min-width: 7rem;
  font-weight: 500;
  color: var(--muted);
}

/* --- A document as it reads once signed ------------------------------- */

.doc-answer {
  margin: 0 0 1rem;
}

.doc-answer:last-child {
  margin-bottom: 0;
}

.doc-answer > .question {
  margin: 0 0 0.2rem;
  font-weight: 500;
}

/* The prompt the question was asked with, kept on the signed document for the
   reason docs/emr.md 4.2.1 gives: a note showing only the prose loses what was
   asked for, and a reader cannot tell a thin answer from a thin question. */
.doc-answer > .hint {
  margin: 0 0 0.35rem;
}

.doc-answer > .answer {
  margin: 0;
  white-space: pre-line;
}

/* The signature line, which is the half a payer looks for first. */
.signature-line {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.6rem 1.25rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--border);
}

.signature-line:last-child {
  border-bottom: 0;
}

.signature-line .who {
  font-weight: 500;
}

@media (max-width: 760px) {
  /* A label to the left of its answer needs a screen wide enough for both.
     Below this it goes above, which is what every other form here does. */
  .document-form .field {
    grid-template-columns: minmax(0, 1fr);
  }

  .document-form .field > label {
    grid-column: 1;
    text-align: left;
    padding-top: 0;
  }

  .document-form .field > :not(label),
  .document-form .field .hint,
  .document-form .field .field-error {
    grid-column: 1;
  }
}

/* --- The patient band -------------------------------------------------- */

/* The band's bar holds two things, one at each end. `gap` rather than a
   margin, so a long name and a long site name keep a space between them when
   the pair wraps on a narrow screen. */
.band-heading {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.25rem 1rem;
}

.band-site {
  font-weight: 500;
  font-size: 0.87rem;
}

/* Who this chart is about, held above whatever is being read or written --
   the block the EMR keeps at the top of every patient screen. Facts only:
   enough to be certain the right chart is open, which is the failure it
   exists to prevent.

   `auto-fit` rather than three fixed columns, because three columns of facts
   at 1440 is two at 1100 and one on a phone, and the breakpoint for that is
   the content's own width rather than a number written here. */
.fact-grid {
  display: grid;
  /* **Column-wise**, four down and four across (2026-10-04, the owner's
     order): who they are and when they arrived, then where they are in
     treatment. Row flow would read MRI, Level of care, Date of birth, LOC
     detail, which is two subjects interleaved.

     Every row the same height, so the spacing cannot depend on what a value
     happens to say: a grid row is as tall as its tallest cell, and one wrapped
     value used to push its row to 39px while its neighbours stayed 21 -- which
     reads as more air between some facts than others. The columns are wide
     enough that no value wraps, which is what keeps that true. */
  grid-auto-flow: column;
  /* `minmax`, not a fixed height: at a desk nothing wraps and every row is
     1.45rem, which is the even spacing this exists for -- but in a narrower
     pane a long name does wrap, and a fixed row clips it over the row beneath
     rather than growing. Column flow means a grown row grows in both columns,
     so the two stay level with each other either way. */
  grid-template-rows: repeat(4, minmax(1.45rem, auto));
  grid-auto-columns: minmax(0, 1fr);
  gap: 0.15rem 1.75rem;
  align-content: start;
}

.fact {
  display: grid;
  /* 7.75rem so "Primary counselor" sits on one line. With a fixed row height
     above, a label that wraps does not make its row taller -- it overlaps the
     row beneath, which is worse than the uneven spacing this was fixing. */
  grid-template-columns: 7.75rem minmax(0, 1fr);
  gap: 0.5rem;
  align-items: baseline;
  /* Tight on purpose (2026-10-04): the whole job of the band is to be taken in
     at a glance before the chart underneath is read.

     **`margin: 0` is most of it.** Each fact is a `<dl>`, which carries a 1em
     margin top and bottom by default -- 96px of air across three rows, which
     is where a header 346px tall was going. Measured before and after. */
  margin: 0;
  padding: 0.05rem 0;
  line-height: 1.3;
}

.fact > dt {
  text-align: right;
  color: var(--muted);
  font-size: 0.87rem;
  /* A label is two or three known words; if one ever outgrows the column it
     should be shortened here rather than silently breaking the rhythm. */
  white-space: nowrap;
}

.fact > dd {
  margin: 0;
  font-weight: 500;
}

@media (max-width: 560px) {
  /* A label beside its value needs room for both; below this the value goes
     under the label, as the fields do at the same sort of width. */
  .fact {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .fact > dt {
    text-align: left;
  }

  /* And the grid loses its 12.5rem floor. Measured at 375: the band has 294px
     inside the card, the portrait and its gap take 108, and a floor of 200
     left the facts 14px wider than the space they had -- clipped rather than
     wrapped, because the page itself was not allowed to scroll. */
  .fact-grid {
    /* One column on a phone, read straight down in the same order. A value
       wraps at this width whatever the column does, so the rows size
       themselves again rather than being clipped to 1.45rem. */
    grid-auto-flow: row;
    grid-template-rows: none;
    grid-template-columns: minmax(0, 1fr);
    grid-auto-rows: auto;
  }

  /* One column, so the strip goes under everything rather than beside it. */
  .band > .chart-strip {
    grid-column: 1;
  }
}

/* The photograph's place, and the chart's two lists ---------------------- */

.band {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1.25rem;
  align-items: start;
}

/* Initials until there is somewhere to put a file (docs/emr.md 3.5.2). Sized
   to a passport-ish portrait so the layout does not move the day a real one
   arrives -- and sized generously (2026-10-04, the owner's ask), because on a
   chart the face is how somebody confirms they have the right patient before
   reading a word of it. */
.portrait {
  display: grid;
  place-items: center;
  /* Big enough to recognise a face at a glance, which is the whole job: the
     chart's reader is confirming they have the right patient before they read
     a word of it. **It sets the header's height**, because the facts beside it
     come to 82px -- so this is a deliberate 50px of header, kept on the owner's
     call (2026-10-04) after being trimmed and put back. The air that was worth
     removing was the 96px of default `<dl>` margin in the facts, not this. */
  width: 8rem;
  height: 9.5rem;
  background: var(--surface-sunk);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--muted);
  font-size: 2.25rem;
  font-weight: 600;
  letter-spacing: 0.04em;
}

/* The coaching roster: a grid of people, each a card you open.

   `auto-fill` rather than `auto-fit`: with three clients on a wide screen,
   `auto-fit` collapses the empty tracks and stretches three cards across the
   window, which reads as a layout fault rather than a short list. `auto-fill`
   keeps the columns and leaves the gap, which is what a roster of three
   actually looks like.

   The card is the target, not a link inside it -- a name three words long
   should not be a smaller thing to hit than the face above it. */
.roster {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  /* **Every card the same size, whatever a name does** (2026-10-11, the
     owner -- one box was 258px against 235px because a double-barrelled
     surname wrapped to two lines).

     `1fr` auto rows in a container with no height of its own resolve to the
     tallest row's content, so every row matches the worst case and the cards
     stop telling you which names are long. It takes `height: 100%` on the
     link below to be worth anything: the `<li>` already stretched to its
     row, but the link inside it -- which is the thing with the border, and
     so the thing you see -- only ever took its own content's height. */
  grid-auto-rows: 1fr;
  gap: 1rem;
}

.roster > li > a {
  display: grid;
  justify-items: center;
  /* Fills the row the `<li>` was already given. Without it the equal rows
     above are invisible -- the cell is the right size and the card inside it
     is not. */
  height: 100%;
  /* The portrait and the two lines of text sit at the top, so a card made
     taller by somebody else's long name grows at the bottom rather than
     spacing its own contents out. */
  align-content: start;
  gap: 0.35rem;
  padding: 0.85rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: inherit;
  text-decoration: none;
  /* Only the hover moves, so a pointer never shifts the grid under itself. */
  transition: border-color 0.12s ease, background 0.12s ease;
}

.roster > li > a:hover,
.roster > li > a:focus-visible {
  border-color: var(--border-strong);
  background: var(--lift-1);
}

.roster-name {
  font-weight: 600;
  text-align: center;
  /* Smaller than the body text, the owner's own suggestion for this: at 1rem
     a twenty-character name needs two lines in a 175px card, and most names
     fit on one at this size. It is a roster of faces rather than prose, so
     the name is a label and not something anybody reads at length. */
  font-size: 0.9rem;
  line-height: 1.25;
  /* One long surname widens its own card and nothing else: the track is
     `1fr` of a filled column, so without this it would widen every column. */
  overflow-wrap: anywhere;
}

.roster-dob {
  color: var(--muted);
  font-size: 0.85rem;
}

@media (pointer: coarse) {
  /* The whole card already clears the 44px minimum by a wide margin; what
     needs saying is the gap, so two cards are not one target with a seam. */
  .roster { gap: 1.1rem; }
}

/* Warnings and diagnoses, a line each under the band.

   They were a panel each beside it, which put two editing forms across the top
   third of every chart -- read once, in the way every time after that. A line
   states them; changing them is a screen behind an Edit link. */
.chart-strip {
  margin-top: 0.7rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--border);
}

/* Under the facts and beside the photograph (2026-10-04, the owner's ask), so
   the header is one block. **Named explicitly**: as a third child of a
   two-column grid it was auto-placed into the first column, under the
   portrait, and its content widened that column until the facts had 288px and
   one column -- which read as the facts having moved, not the strip. */
.band > .chart-strip {
  grid-column: 2;
}

.strip-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem 0.6rem;
  margin: 0 0 0.25rem;
}

.strip-line:last-child {
  margin-bottom: 0;
}

.strip-label {
  color: var(--muted);
  font-size: 0.87rem;
  min-width: 5.5rem;
}

/* Each entry stays whole when the line wraps: an allergy broken across two
   lines mid-phrase is the one that gets misread. */
.warning-chip,
.diagnosis-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
}

.warning-chip + .warning-chip::before,
.diagnosis-chip + .diagnosis-chip::before {
  content: "\00b7";
  color: var(--muted);
  margin-right: 0.35rem;
}

.strip-line .button {
  margin-left: auto;
}

.warnings {
  list-style: none;
  margin: 0;
  padding: 0;
}

.warning {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--border);
}

.warning:last-child {
  border-bottom: 0;
}

/* The kind is the word somebody scans for, so it is the loud half. Amber for
   text on the page, never the surface colour -- the mistake `.state-admitted`
   made, measured at 1.39:1. */
.warning .kind {
  color: var(--warn-ink);
  font-weight: 600;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.warning .says {
  flex: 1 1 12rem;
}

.warning form {
  margin: 0;
}

/* Adding one is a sentence, not a page: a row of controls under the list it
   adds to. Wraps rather than scrolling, so a phone gets the same form. */
.inline-add {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-top: 0.75rem;
}

.inline-add input[type="text"] {
  flex: 1 1 10rem;
  width: auto;
}

.inline-add select {
  flex: 0 1 9rem;
}

.inline-add .principal {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  white-space: nowrap;
  color: var(--muted);
  font-size: 0.87rem;
}

/* A label a screen reader reads and the page does not show. The inline add
   rows use placeholders for sighted readers, and a placeholder is not a label.
   The clip-rect pattern rather than `display: none`, which hides it from
   assistive technology too. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The dialog's own title, where the page has an <h1> above the same partial. */
.panel-title {
  margin: 0 0 0.75rem;
  font-size: 1.05rem;
}

/* A datalist's own menu is the browser's, so there is nothing to style for the
   suggestions themselves -- this only keeps the code box from being as wide as
   the description beside it. */
.inline-add input[data-icd10] {
  flex: 0 1 14rem;
}

/* --- The chart's tabs --------------------------------------------------- */

/* Ordinary links, so a tab is bookmarkable, opens in a new window and works
   with the script blocked -- the chart is a page per tab rather than a panel
   somebody has to re-find. Wraps rather than scrolls: six words on a phone is
   two lines, where a sideways-scrolling tab strip hides whichever tab happens
   to be last. */
.chart-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin: 0.9rem 0 0.75rem;
  border-bottom: 1px solid var(--border);
}

.chart-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.85rem;
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0;
  color: var(--muted);
  text-decoration: none;
  /* The strip's own border sits under these, so the current tab can cover it
     and read as the front of the stack rather than as a highlighted link. */
  margin-bottom: -1px;
}

.chart-tab:hover {
  color: var(--text);
  background: var(--surface-sunk);
}

.chart-tab.current {
  background: var(--surface);
  border-color: var(--border);
  border-bottom: 1px solid var(--surface);
  color: var(--text);
  font-weight: 500;
}

/* The kinds of document a tab holds: the name, how many are filed, and the
   way to start one. A row rather than a table -- three things, one of which is
   a control, is a list. */
.kinds {
  list-style: none;
  margin: 0;
  padding: 0;
}

.kind-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.9rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
}

.kind-row:last-child {
  border-bottom: 0;
}

.kind-row > a {
  flex: 1 1 14rem;
  font-weight: 500;
}

.kind-row form {
  margin: 0;
}

@media (pointer: coarse) {
  /* The same 44px minimum every other control here takes on a touch screen. */
  .chart-tab {
    min-height: 44px;
  }
}

/* --- Running a group ---------------------------------------------------- */

/* Opening a group is five short answers, so they sit on one or two lines
   rather than down a column. */
.group-start {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0 1.25rem;
}

.group-start .field.wide,
.group-start .tasks {
  grid-column: 1 / -1;
}

/* Who is in the room: a list of names to tick, two or three across, because
   a group is eight people and a column of eight is a scroll. */
.candidates {
  list-style: none;
  margin: 0.75rem 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 0.15rem 1rem;
}

.candidates label {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  padding: 0.3rem 0;
}

@media (pointer: coarse) {
  /* The 44px minimum every tick target here takes on a touch screen. */
  .candidates label {
    min-height: 44px;
    align-items: center;
  }
}

/* A tick list on a clinical document runs across rather than down: the mental
   status lists are eighteen words somebody scans, and a single column of them
   is most of a screen each. Three across at a desk, one on a phone, decided by
   the width a label needs rather than by a breakpoint.

   **The selector is `> div`, not `ul`.** Django 5 renders a checkbox or radio
   group as nested divs, so a rule written for a list matches nothing at all --
   which is what happened here first, and looked exactly like the stylesheet not
   having reloaded. */
.document-form .field > div {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0 1rem;
}

.document-form .field > div label {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.15rem 0;
  font-weight: 400;
}

@media (pointer: coarse) {
  .document-form .field > div label {
    min-height: 44px;
    align-items: center;
  }
}
