/* AccounTrac v3 — v2-inspired design system (approved 07/09/26, RESTYLE-PORT.md).
   One hand-written file; no build step, no external assets (CSP: self only).
   Identity: teal #00827e / AC-blue #0c437c gradient app-bar, icon-over-label nav,
   soft-elevation white surfaces, Montserrat (self-hosted), light-only.
   Every selector the templates already use is preserved — unported screens inherit.

   07/26/26 — the "Minimalist Modern" pass (shipped on the activity page 07/25 behind
   a .exp wrapper) went APP-WIDE at Casey's word: gradient-teal section heads with a
   stub underline, softer layered card elevation on a 14px radius, tinted card header
   band, gradient primary buttons and "on" pills, rounder inputs, a one-shot card rise
   on load. Those rules are merged into the sections below — there is no .exp wrapper
   any more, and no separate block to keep in sync. */

/* ---------- tokens ---------- */

:root {
  --ground:      #eef3f4;   /* page background */
  --surface:     #ffffff;   /* cards, table bodies */
  --surface-2:   #f3f7f7;   /* hover, wells, inputs */
  --well:        #e9f0f0;
  --ink:         #17262a;
  --ink-soft:    #45585c;
  --ink-faint:   #7a8b8e;
  --line:        #d9e4e5;   /* hairlines */
  --line-strong: #c2d1d2;   /* input borders */

  --teal:        #00827e;   /* brand primary */
  --teal-deep:   #006f6b;
  --teal-bright: #37a89f;
  --teal-tint:   #e0efee;
  --blue:        #0c437c;   /* AC blue */
  --blue-deep:   #0a3763;
  --grad: linear-gradient(97deg, #0c437c 0%, #0a6f77 55%, #13938a 100%);

  --crit:        #c0392b;   /* danger / overdue */
  --crit-tint:   #fae9e6;
  --warn-tint:   #fdf3e0;
  --warn-ink:    #8a5a12;   /* amber text on --warn-tint (.pill.note) */
  --ok:          #00d632;   /* saved (Casey 10/02/26: "a tint of 00D632") */
  --ok-tint:     #dcf8e3;
  --ok-line:     #9fe6b3;
  --ok-ink:      #0b5d23;   /* text on --ok-tint, 7:1 */

  --shadow-sm:  0 1px 2px rgba(11,40,42,.06), 0 1px 3px rgba(11,40,42,.05);
  --shadow-md:  0 2px 4px rgba(11,40,42,.06), 0 6px 20px rgba(11,40,42,.08);
  --shadow-pop: 0 6px 24px rgba(11,40,42,.18), 0 2px 6px rgba(11,40,42,.12);
  /* cards sit on their own, softer elevation (lifted on hover) */
  --shadow-card:    0 1px 2px rgba(11,40,42,.05), 0 8px 24px rgba(11,40,42,.07);
  --shadow-card-hi: 0 2px 6px rgba(11,40,42,.07), 0 18px 40px rgba(11,40,42,.12);
  --grad-teal: linear-gradient(135deg, var(--teal), var(--teal-bright));
  --grad-ink:  linear-gradient(120deg, var(--teal-deep), var(--teal-bright));
  --radius: 10px;        /* menus, filter bar, drawer */
  --radius-card: 14px;   /* cards — deliberately rounder than the chrome */

  --f-display: 'Montserrat', system-ui, sans-serif;
  --f-body:    'Montserrat', system-ui, sans-serif;
  /* Numeric/ID display (AT IDs, dates, phones, money, counts): the app's own Montserrat
     with tabular figures, NOT the mono family — Casey doesn't want dotted-zero digits
     reading as terminal output (07/28/26). Columns still line up via tabular-nums. */
  --f-num:     'Montserrat', system-ui, sans-serif;
  /* IBM Plex Mono stays vendored for genuine machine/precision contexts: activity/audit
     code chips (mixed-letter codes like LS2, PR/CFM) and the TOTP secret (base32,
     disambiguating 0/O and 1/l matters more than de-terminaling it there). */
  --f-mono:    'IBM Plex Mono', 'Menlo', monospace;

  /* The two levers CSS has over NATIVE control chrome — the date picker's calendar
     panel, select popups, scrollbars, checkbox ticks. That UI is drawn by the browser
     and no selector reaches inside it (Casey asked, 08/06/26). color-scheme pins it
     light, which is what this design is; accent-color tints the selected day and the
     checkbox fills teal. Both inherit, so this is the whole app in two lines. */
  color-scheme: light;
  accent-color: var(--teal);

  /* Calendar glyph for date fields, matching the app's 2px-stroke line icons — one resting,
     one for hover/focus. Data URIs, so they're covered by the CSP's `img-src 'self' data:`
     and app.css stays the only file. TWO of them because a data URI can't read a custom
     property: the stroke colours below are literal copies of --ink-faint (#7a8b8e) and
     --teal (#00827e) and must be re-copied if those tokens ever change. (A mask-image
     would take the colour from a token — tried first, 08/06/26, and Chrome ignores mask
     on ::-webkit-calendar-picker-indicator; background-image is what it honours.) */
  --icon-calendar:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237a8b8e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='5' width='18' height='16' rx='3'/%3E%3Cpath d='M3 10h18M8 3v4M16 3v4'/%3E%3C/svg%3E");
  --icon-calendar-on: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2300827e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='5' width='18' height='16' rx='3'/%3E%3Cpath d='M3 10h18M8 3v4M16 3v4'/%3E%3C/svg%3E");

  /* Motion easing tokens (see .claude/skills/styling/SKILL.md "Motion" section for the
     full standard — durations, transform/opacity-only, no ease-in). Three tiers, picked
     by what's moving, not by the exact duration: -sm for small controls/one-shot
     accents, -md for popover/dropdown-scale reveals, -lg for drawer/modal-scale
     entrances. All are out-curves (fast start, decelerate to rest) — never ease-in on
     UI. The exact cubic-bezier numbers are this app's own choice: only the DURATION
     numbers were harvested from emilkowalski's skills repo (not installed here). */
  --ease-out-sm: cubic-bezier(.16, 1, .3, 1);
  --ease-out-md: cubic-bezier(.22, 1, .36, 1);
  --ease-out-lg: cubic-bezier(.19, 1, .22, 1);
}

/* ---------- reset / base ---------- */

*, *::before, *::after { box-sizing: border-box; }
/* The UA's [hidden] rule is a bare `display: none` on the element selector, so ANY class
   with an explicit display beats it — `.btn { display: inline-flex }` meant a button set
   to hidden stayed on screen (found 08/05/26 in the patient modal head, where Edit
   refused to disappear in the edit pane; app.css:669 was an earlier one-off patch of the
   same bug). Setting `hidden` in JS should mean hidden, everywhere. */
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; }
/* Keep the scrollbar's space reserved so a page growing past the fold (Run Report filling
   in results) doesn't nudge every centered card sideways (Casey, 10/03/26). A no-op where
   scrollbars overlay (default macOS, phones). */
html { scrollbar-gutter: stable; }
html, body { background: var(--ground); } /* fill the viewport — no stray band below short pages */
body {
  margin: 0;
  color: var(--ink);
  font: 400 14.5px/1.55 var(--f-body);
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; display: block; }
h1, h2, h3 { font-family: var(--f-display); margin: 0; line-height: 1.2; }
a { color: var(--teal); text-decoration: none; }
@media (hover: hover) {
  a:hover { text-decoration: underline; text-underline-offset: 2px; }
}
:focus-visible { outline: 2px solid var(--teal-bright); outline-offset: 2px; border-radius: 4px; }

/* ---------- app bar ---------- */

.appbar {
  background: var(--grad); color: #fff; display: flex; align-items: stretch;
  padding: 0 20px 0 0; box-shadow: var(--shadow-md); position: relative; z-index: 5;
}
.brand {
  display: flex; align-items: center; gap: 11px; padding: 0 18px; margin-right: 8px;
  background: #fff; border-right: 1px solid var(--line);
  /* .appbar is a flex row; without this, a tight viewport shrinks the logo <img> below
     its intrinsic aspect ratio instead of leaving it alone (flex's default min-width:auto
     lets replaced elements compress) — found while fitting the appbar at 375px: the brand
     mark was silently squashing itself narrower rather than the layout ever truly
     overflowing. Always safe: does nothing where there's already slack. */
  flex-shrink: 0;
}
.brand-logo { height: 44px; width: auto; display: block; }

.mainnav { display: flex; align-items: stretch; gap: 2px; margin: 0 auto 0 10px; }
.mainnav a, .mainnav button {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  color: rgba(255,255,255,.82); text-decoration: none; padding: 9px 18px 7px; min-width: 82px;
  font-size: 11.5px; font-weight: 500; border-bottom: 3px solid transparent;
  transition: color .15s, background .15s, border-color .15s;
  /* reset for .mainnav button, which the .mainnav a rules above were not written for
     (Feedback, 09/15/26 — a nav item that opens a dialog instead of navigating) */
  border-width: 0 0 3px; background-color: transparent; font-family: inherit; cursor: pointer;
}
.mainnav a svg, .mainnav button svg { height: 20px; width: auto; }
@media (hover: hover) {
  .mainnav a:hover, .mainnav button:hover { color: #fff; background: rgba(255,255,255,.08); text-decoration: none; }
}
.mainnav a.active { color: #fff; border-bottom-color: #fff; background: rgba(255,255,255,.12); }

/* min-width:0 (+ the input's max-width below) lets the search give up pixels instead of
   pushing the bar past the viewport. Without it the appbar's natural width is ~962px, so
   every width from ~820 (where the nav pass stops) to ~962 scrolled the page sideways by
   a couple of px — 960 being exactly where Evelyn works. */
.appbar-right { display: flex; align-items: center; gap: 12px; min-width: 0; }

/* user menu: avatar+nickname chip in the app bar, opens a dropdown (admin / sign out) */
.user-menu { position: relative; }
.user-menu summary { list-style: none; cursor: pointer; }
.user-menu summary::-webkit-details-marker { display: none; }
.user-menu-trigger {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 10px 5px 5px; border-radius: 999px;
  background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.22);
  transition: background .15s;
}
.user-menu[open] .user-menu-trigger { background: rgba(255,255,255,.26); }
@media (hover: hover) {
  .user-menu-trigger:hover { background: rgba(255,255,255,.26); }
}
.user-menu-trigger .avatar {
  display: flex; align-items: center; justify-content: center; flex: none;
  width: 26px; height: 26px; border-radius: 50%;
  /* the user's own circle/letter colors (.u-<id> from /avatar-colors.css — Casey,
     09/27/26: a color picked on My account should show here too). Every user has one
     (a pick, or the automatic default by id), so this matches their activity-log circle;
     the white fallback only shows if that stylesheet fails. The ring separates any
     color from the bar. */
  background: var(--av, rgba(255,255,255,.92)); color: var(--av-ink, var(--blue-deep));
  box-shadow: 0 0 0 1.5px rgba(255,255,255,.85);
  font: 700 11px var(--f-display); letter-spacing: .01em;
  overflow: hidden;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* account page: bigger preview beside the upload form */
.avatar-row { display: flex; align-items: flex-start; gap: 18px; }
.avatar-lg {
  display: flex; align-items: center; justify-content: center; flex: none;
  width: 64px; height: 64px; border-radius: 50%; overflow: hidden;
  background: var(--blue-deep); color: #fff;
  font: 700 22px var(--f-display); letter-spacing: .01em;
}
.avatar-forms { flex: 1 1 auto; min-width: 0; }
/* Admin user form (Casey, 08/19/26): the avatar and its upload controls as a strip across
   the TOP of the Profile card, with the identity fields full-width beneath. It began life
   as a narrow left column, which stranded a third of the card empty — a 64px avatar and
   three short controls cannot fill a column as tall as eleven fields, and the fields lost
   a quarter of their width to it. A rule under the strip separates it from the fields
   without needing a second card. */
.profile-head {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding-bottom: 16px; margin-bottom: 16px; border-bottom: 1px solid var(--line);
}
.profile-avatar-actions { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.profile-avatar-row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.avatar-file { font-size: 11.5px; color: var(--ink-soft); }
.avatar-note { font-size: 12px; color: var(--ink-faint); margin: 0; }
.user-menu-trigger .user-menu-name { font: 500 13px var(--f-body); color: #fff; }
.user-menu-trigger .caret { width: 13px; height: 13px; stroke-width: 2.4; flex: none;
  color: rgba(255,255,255,.75); transition: transform .18s; }
.user-menu[open] .user-menu-trigger .caret { transform: rotate(180deg); }

.user-menu-panel {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 30;
  min-width: 210px; max-width: calc(100vw - 24px);
  background: var(--surface); color: var(--ink); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-pop); padding: 6px; overflow: hidden;
}
.user-menu-head { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px 10px; }
.user-menu-head .u { font: 600 13.5px var(--f-display); color: var(--ink); }
.user-menu-head .role { font-size: 9.5px; text-transform: uppercase; letter-spacing: .09em;
  font-weight: 600; color: var(--ink-faint); }
.user-menu-divider { height: 1px; background: var(--line); margin: 4px 2px; }
.user-menu-item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  font: 500 13.5px var(--f-body); color: var(--ink); background: none; border: 0; cursor: pointer;
  padding: 8px 10px; border-radius: 6px; text-decoration: none;
}
.user-menu-item svg { width: 17px; height: 17px; stroke-width: 1.9; flex: none; color: var(--ink-faint); }
@media (hover: hover) {
  .user-menu-item:hover { background: var(--teal-tint); color: var(--teal-deep); text-decoration: none; }
  .user-menu-item:hover svg { color: var(--teal-deep); }
  .user-menu-item.signout:hover { background: var(--crit-tint); color: var(--crit); }
  .user-menu-item.signout:hover svg { color: var(--crit); }
}
.user-menu-form { margin: 0; }

/* What's New: dashboard button with an unread dot, plus the changelog dialog. Tags reuse .pill; these only recolor it. */
.wn-btn { display: inline-flex; align-items: center; gap: 6px; }
.wn-btn svg { width: 15px; height: 15px; stroke-width: 1.9; flex: none; }
.wn-dot { width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--teal); box-shadow: 0 0 0 3px var(--teal-tint); }
.wn-dot[hidden] { display: none; }
.dlg-close { margin-left: auto; align-self: center; flex: none; display: grid; place-items: center;
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 6px; background: none;
  color: var(--ink-faint); cursor: pointer; }
.dlg-close svg { width: 16px; height: 16px; stroke-width: 2; }
@media (hover: hover) { .dlg-close:hover { background: var(--teal-tint); color: var(--teal-deep); } }
.wn-body { font-size: 13.5px; line-height: 1.5; color: var(--ink); }
.wn-date { margin: 0; font: 700 15px var(--f-display); color: var(--ink); }
.wn-intro { margin: 4px 0 14px; color: var(--ink-soft); }
/* One section per week, newest first: a rule between weeks, and a short week (no intro or
   group headings) starts its list just under the date. */
.wn-entry + .wn-entry { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line); }
.wn-date + .wn-list { margin-top: 8px; }
.wn-group { margin: 16px 0 6px; font: 600 10.5px var(--f-display); letter-spacing: .09em;
  text-transform: uppercase; color: var(--ink-faint); }
.wn-list { margin: 0; padding: 0; list-style: none; }
.wn-list li { padding: 6px 0; border-top: 1px solid var(--line); }
.wn-list li:first-child { border-top: 0; }
.wn-tag { font-size: 9.5px; padding: 2px 8px; margin-right: 6px; vertical-align: 1px; }
.wn-new { color: var(--teal-deep); border-color: #b7dedc; background: var(--teal-tint); }
.wn-improved { color: var(--blue); border-color: #c5d4e6; background: #e9eff7; }
.wn-fixed { color: var(--warn-ink); border-color: #edd9ae; background: var(--warn-tint); }

/* global patient search in the app bar (RESTYLE-PORT.md §12) */
/* min-width:0 is what actually lets it shrink — min-width:auto on a flex item floors it
   at the input's 200px, and the overflow then lands on the user menu beside it. */
.nav-search { position: relative; min-width: 0; }
/* [type=search] matters: the global field rule (input[type=search], ~line 204) ties a
   bare `.nav-search input` on specificity and sits later in the file, so it was
   repainting this field with the light --surface-2 "paper" background (the white-when-
   idle look Casey flagged 07/11). appearance:none also strips WebKit's UA field paint. */
.nav-search input[type=search] {
  font: 500 12.5px/1 var(--f-body); color: #fff; width: 200px; max-width: 100%; min-width: 0;
  background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.22);
  padding: 8px 12px; border-radius: 8px; transition: background .15s, width .15s;
  -webkit-appearance: none; appearance: none;
}
.nav-search input::placeholder { color: rgba(255,255,255,.65); }
.nav-search input:focus { background: rgba(255,255,255,.24); outline: none; width: 240px; }
.nav-search-menu {
  position: absolute; top: calc(100% + 6px); right: 0; width: 320px; max-height: 340px;
  overflow-y: auto; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-pop); z-index: 30; padding: 4px;
}
.nav-search-row {
  display: flex; flex-direction: column; gap: 1px; padding: 8px 10px;
  border-radius: 6px; color: var(--ink); text-decoration: none;
}
.nav-search-row.active { background: var(--teal-tint); text-decoration: none; }
@media (hover: hover) {
  .nav-search-row:hover { background: var(--teal-tint); text-decoration: none; }
}
.nav-search-row .n { font-weight: 600; font-size: 13px; }
.nav-search-row .m { font-family: var(--f-num); font-variant-numeric: tabular-nums; font-size: 11.5px; color: var(--ink-soft); }
.nav-search-none { padding: 10px 12px; font-size: 12.5px; color: var(--ink-faint); }

/* admin sub-nav */
.subnav {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  padding: 0 28px;
  display: flex;
  gap: 4px;
  overflow-x: auto;
}
.subnav a {
  padding: 10px 13px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-soft);
  white-space: nowrap;
  border-bottom: 2px solid transparent;
}
@media (hover: hover) {
  .subnav a:hover { color: var(--ink); text-decoration: none; }
}
.subnav a.active { color: var(--teal-deep); border-bottom-color: var(--teal); font-weight: 600; }

/* ---------- page scaffold ---------- */

.page { max-width: 1260px; margin: 0 auto; padding: 26px 26px 60px; }
.page-head {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin: 0 0 18px;
}
.page-head h1 { font-size: 28px; font-weight: 700; letter-spacing: -.02em; line-height: 1; }
.page-head .count { font-size: 13.5px; color: var(--ink-faint); font-weight: 500; }
.page-head .rule { flex: 1; height: 1px; background: var(--line-strong); align-self: center; }
.page-head .actions { display: flex; gap: 8px; }

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

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  transition: box-shadow .3s var(--ease-out-md), transform .3s var(--ease-out-md);
}
.card + .card { margin-top: 14px; }
/* A ROW of side-by-side cards. `.card + .card` only sees ADJACENT siblings, so wrapping
   two cards in a grid silently breaks the chain and the cards above and below the row end
   up flush against it — 0px where every other seam is 14px (Casey, 08/19/26: "the space
   between the cards is not even they are jammed up vertically"). This carries the same
   rhythm across the wrapper. Put .card-row on any element that holds a row of cards,
   whether or not it is also a .grid. */
.card-row { margin-top: 14px; }
.card-row + .card, .card-row + .card-row { margin-top: 14px; }
/* only free-standing page cards lift — not the patient strip, and not cards
   nested inside another card's body */
@media (hover: hover) {
  .page > .card:not(.patient-strip):hover { box-shadow: var(--shadow-card-hi); }
}
.card-head {
  padding: 11px 18px;
  border-bottom: 1px solid var(--line);
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  background: linear-gradient(180deg, #f4faf9, var(--surface));
  display: flex;
  align-items: baseline;
  gap: 12px;
}
/* section label: gradient text over a stubby underline bar */
.card-head h2 {
  position: relative; display: inline-block; padding-bottom: 8px;
  font-family: var(--f-display); font-weight: 700; font-size: 14px;
  letter-spacing: .03em;
  background: var(--grad-ink);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.card-head h2::after {
  content: ""; position: absolute; left: 0; bottom: 0;
  width: 28px; height: 3px; border-radius: 2px; background: var(--grad-teal);
}
.card-head .hint { font-size: 12px; color: var(--ink-faint); }
.card-body { padding: 15px 18px; }
/* Form cards get their own, roomier vertical padding, balanced OPTICALLY rather than
   literally: a field label is 10.5px uppercase sitting in a 16.3px line box, so ~4px of
   leading rides above its caps and a literal top padding always reads ~4px larger than
   the same number underneath. 21 + 4 = 25 up top, a flat 25 below.

   Casey, 08/24/26, on the Patient and Responsible party cards — the two with a single
   row of fields, where the card is short enough that the edges are all you see. The
   first pass here balanced them at 15/15, which was the wrong target: it took 14px OUT
   of every card ("too tight"). 25/25 keeps the bottom room the cards shipped with and
   brings the top up to meet it, rather than cutting the top down to meet the bottom.

   Scoped to card bodies that OPEN on a labelled field, so every form card gets it and no
   table or toolbar card does. The <form> variants are the same two shapes wrapped in a
   form element, which is how /account and /admin/settings build their cards — the grid
   there is not :first-child because the hidden CSRF input precedes it. */
.card-body:has(> .field:first-child > label:first-child),
.card-body:has(> .grid:first-child > .field:first-child > label:first-child),
.card-body:has(> form:first-child > .field > label:first-child),
.card-body:has(> form:first-child > .grid > .field:first-child > label:first-child) { padding-top: 21px; padding-bottom: 25px; }
.card-body.tight { padding: 0; overflow: hidden; border-radius: 0 0 var(--radius-card) var(--radius-card); }
.card > .card-body.tight:first-child { border-radius: var(--radius-card); }

/* ---------- collapsible card ----------
   A page card whose body stays shut until asked for — for a secondary flow that
   would otherwise push the primary one down the screen (Casey, 08/11/26: the bulk
   batch picker on /letters, below "Find a letter batch"). Built on <details> like
   .user-menu and .sortmenu: no JS, CSP-safe, and it needs no open/close state on
   the server. Usage:
     <details class="card card-collapse">
       <summary class="card-head"><h2>…</h2><span class="hint">…</span>{chevron svg}</summary>
       <div class="card-body">…</div>
     </details>
   `.page > .card` is class-based, so card-rise and the hover lift still apply. Note
   a closed <details> hides its content from find-in-page and from any JS that
   MEASURES layout at load — the /letters scripts only read .checked/textContent, so
   they were unaffected; check that before collapsing a card with sized-at-load JS. */
.card-collapse > summary { list-style: none; cursor: pointer; }
.card-collapse > summary::-webkit-details-marker { display: none; }
/* collapsed, the head IS the card — its divider would sit on the card's own border */
.card-collapse:not([open]) > .card-head {
  border-bottom-color: transparent;
  border-radius: var(--radius-card);
}
.card-chev {
  margin-left: auto; align-self: center; flex: none;
  width: 18px; height: 18px; stroke-width: 2.2; color: var(--ink-faint);
  transition: transform .2s var(--ease-out-md), color .12s var(--ease-out-sm);
}
@media (hover: hover) {
  .card-collapse > summary:hover .card-chev { color: var(--teal); }
}
.card-collapse[open] > summary .card-chev { transform: rotate(180deg); color: var(--teal); }
/* the reveal is opacity+transform only — no height animation (Motion rules in the
   styling skill); <details> cannot animate to auto height without JS regardless */
@media (prefers-reduced-motion: no-preference) {
  .card-collapse[open] > .card-body { animation: card-reveal .2s var(--ease-out-md) both; }
}
@keyframes card-reveal { from { opacity: 0; transform: translateY(-6px); } }

/* ---------- report-tile (picker cards, e.g. /reports) ----------
   A .card variant for "pick one of these" grids: icon chip + title/description +
   trailing arrow, in place of a bare title-only tile (Casey, 07/28/26: "buttons
   look unfinished"). Reusable for any future picker screen, not a one-off. */
.report-tile { display: flex; align-items: flex-start; gap: 14px; padding: 18px; text-decoration: none; color: inherit; }
.report-tile-icon {
  flex: none; width: 42px; height: 42px; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  background: var(--teal-tint); color: var(--teal-deep);
}
.report-tile-icon svg { width: 22px; height: 22px; stroke-width: 1.8; }
.report-tile-body { flex: 1; min-width: 0; }
.report-tile-body h2 { font-family: var(--f-display); font-weight: 700; font-size: 15px; color: var(--ink); margin: 0 0 4px; }
.report-tile-body p { margin: 0; }
.report-tile-arrow { flex: none; width: 18px; height: 18px; align-self: center; color: var(--ink-faint); transition: transform .15s, color .15s; }
/* Hover reads as a BUTTON, not a text link (Casey, 08/05/26: "more like buttons
   rather than underlining the report names"). The whole tile is an <a>, so the
   global a:hover underline was striking through the title — kill it here and put
   the affordance on the surface instead: teal edge, lift, filled icon chip, and a
   press state so a click feels like one. .card's own .3s transition is too slow to
   read as a control, hence the shorter override.
   Hover and focus-visible are split (rather than a shared selector) so the hover
   half can live behind @media (hover: hover) — keyboard/focus-visible treatment is
   unaffected on touch devices. */
.report-tile { transition: border-color .15s, box-shadow .15s, transform .15s; }
.report-tile:focus-visible {
  text-decoration: none;
  border-color: var(--teal);
  box-shadow: var(--shadow-card-hi);
  transform: translateY(-1px);
}
.report-tile:focus-visible .report-tile-body h2 { color: var(--teal-deep); }
.report-tile:focus-visible .report-tile-icon { background: var(--teal); color: #fff; }
.report-tile:focus-visible .report-tile-arrow { color: var(--teal); transform: translateX(3px); }
@media (hover: hover) {
  .report-tile:hover {
    text-decoration: none;
    border-color: var(--teal);
    box-shadow: var(--shadow-card-hi);
    transform: translateY(-1px);
  }
  .report-tile:hover .report-tile-body h2 { color: var(--teal-deep); }
  .report-tile:hover .report-tile-icon { background: var(--teal); color: #fff; }
  .report-tile:hover .report-tile-arrow { color: var(--teal); transform: translateX(3px); }
}
.report-tile:active { transform: translateY(0); box-shadow: var(--shadow-card); }
.report-tile-icon { transition: background .15s, color .15s; }

/* ---------- dashboard (docs/SPEC-DASHBOARD.md) ---------- */

.dash-greeting { margin: 0 0 18px; display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.dash-greeting h1 { font-size: 26px; font-weight: 700; letter-spacing: -.02em; }
.dash-greeting .date { margin: 4px 0 0; font-size: 14px; }

/* Hero: the one number every role lands on, bigger than a regular .stat so it reads
   first — plain .card chrome (no bespoke gradient surface) keeps it inside the same
   system as every other card on the page. */
.hero {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: 16px; padding: 24px 26px; margin-bottom: 18px;
}
.hero-value { font: 700 44px/1 var(--f-display); }
.hero-label {
  margin-top: 6px; font: 600 11px var(--f-display); text-transform: uppercase;
  letter-spacing: .07em; color: var(--ink-faint);
}
.hero-sub { margin-top: 6px; font: 600 14px var(--f-body); }
.hero-sub.crit { color: var(--crit); }

/* .stat: the reusable tile — big number, uppercase label (the th voice), optional
   sub-stat line(s). `.card` supplies the surface/border/radius/shadow and (for a direct
   .page child) the entrance rise; .stat only lays out its own contents and, when it's a
   link (an <a>, not a <div>), adds its own snappier hover/focus treatment — same split
   .report-tile uses just above. A non-link .stat (Logged today, the safety-net tile,
   Unassigned) gets none of that: no :hover-only rule ever targets a bare div. */
.stat { display: flex; flex-direction: column; padding: 18px 20px; text-decoration: none; color: inherit; }
.stat-value { font: 700 36px/1 var(--f-display); }
.stat-label {
  margin-top: 8px; font: 600 11px var(--f-display); text-transform: uppercase;
  letter-spacing: .07em; color: var(--ink-faint);
}
.stat-sub { margin-top: 4px; font: 600 13px var(--f-body); color: var(--ink-soft); }
.stat-sub.crit { color: var(--crit); }
.stat-sub.muted { color: var(--ink-faint); font-weight: 500; }
.stat.crit .stat-value { color: var(--crit); }
a.stat { transition: border-color .15s, box-shadow .15s, transform .15s; }
a.stat:focus-visible { text-decoration: none; border-color: var(--teal); box-shadow: var(--shadow-card-hi); transform: translateY(-1px); }
@media (hover: hover) {
  a.stat:hover { text-decoration: none; border-color: var(--teal); box-shadow: var(--shadow-card-hi); transform: translateY(-1px); }
}
a.stat:active { transform: translateY(0); box-shadow: var(--shadow-card); }

.dash-section { margin: 22px 0 10px; font: 700 14.5px var(--f-display); color: var(--ink); }
.dash-quick { margin-bottom: 4px; }

/* By client: a compact list rather than table.ledger's full chrome — each row IS the
   link (no table-row-as-link hack needed), nickname left, count right. Collapsed by
   default (Casey, 08/13/26) via .card-collapse, so it carries its own top margin —
   the .dash-section h2 that used to space it off the by-rep grid is now the card head. */
.dash-by-client { margin-top: 22px; }
.dash-client-list { padding: 4px; }
.dash-client-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 11px 14px; border-radius: 8px; color: inherit; text-decoration: none;
  transition: background .12s;
}
.dash-client-row + .dash-client-row { margin-top: 1px; }
.dash-client-name { font-weight: 500; }
.dash-client-count { font: 700 15px var(--f-display); color: var(--ink-soft); }
@media (hover: hover) {
  .dash-client-row:hover { background: var(--surface-2); text-decoration: none; }
}
.dash-client-row:focus-visible { background: var(--teal-tint); text-decoration: none; }
.dash-empty { padding: 10px 14px; }

/* ---------- env banner ----------
   ENV_BANNER (local/test servers only, Casey 10/02/26): hazard-striped amber strip on top
   of every page, sign-in included, so a test box is never mistaken for production. */
.env-banner {
  position: relative; z-index: 60;
  padding: 4px 16px;
  text-align: center;
  font: 700 11.5px/1.5 var(--f-body); letter-spacing: .08em; text-transform: uppercase;
  color: #3a2700;
  background: repeating-linear-gradient(135deg, #ffd34d 0 14px, #ffc61a 14px 28px);
  border-bottom: 1px solid #d9a400;
}

/* ---------- flash ---------- */

.flash {
  margin: 0 0 16px;
  padding: 11px 14px;
  border-radius: 8px;
  font-size: 13.5px;
  border: 1px solid;
  animation: slip-in .18s var(--ease-out-sm);
}
/* Success reads green, not brand teal (Casey 10/02/26 — "it blends in now"): it has to
   stand apart from the teal UI around it to register as "that worked". */
.flash.notice { background: var(--ok-tint); border-color: var(--ok-line); color: var(--ok-ink); border-left: 5px solid var(--ok); font-weight: 600; }
.flash.notice::before { content: "\2713"; margin-right: 8px; font-weight: 700; color: var(--ok-ink); }
.flash.alert  { background: var(--crit-tint); border-color: #ecc8c2; color: var(--crit); border-left: 3px solid var(--crit); }
@keyframes slip-in { from { opacity: 0; transform: translateY(-4px); } }

/* ---------- reveal: a one-time secret (temporary password) ----------
   Casey 09/22: "more obvious", "formatted like terminal text", with a copy button.
   The one dark surface in a light-only system, on purpose: it is the thing to copy. */

.reveal {
  margin: 0 0 18px;
  padding: 16px 18px 18px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--teal);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  animation: slip-in .18s var(--ease-out-sm);
}
.reveal-head h2 { font-size: 15px; font-weight: 700; color: var(--ink); }
.reveal-head p { margin: 4px 0 12px; font-size: 13px; color: var(--ink-soft); }
.reveal-term {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 12px 12px 16px;
  background: var(--ink);
  border-radius: 8px;
  font-family: var(--f-mono);
  font-size: 17px;
  line-height: 1.4;
}
.reveal-prompt { color: var(--teal-bright); user-select: none; }
.reveal-term code {
  flex: 1; min-width: 0;
  font: inherit; color: #f4faf9; letter-spacing: .02em;
  white-space: nowrap; overflow-x: auto; user-select: all;
}
@media (max-width: 430px) {
  .reveal-term { font-size: 15px; gap: 8px; padding-left: 12px; }
}
.reveal-copy {
  flex: none;
  padding: 6px 12px;
  font-family: var(--f-display); font-size: 12px; font-weight: 700;
  color: #f4faf9; background: transparent;
  border: 1px solid rgba(244,250,249,.35); border-radius: 6px;
  cursor: pointer;
  transition: background-color .15s var(--ease-out-sm), border-color .15s var(--ease-out-sm);
}
@media (hover: hover) {
  .reveal-copy:hover { background: rgba(244,250,249,.1); border-color: rgba(244,250,249,.6); }
}
.reveal-copy:focus-visible { outline: 2px solid var(--teal-bright); outline-offset: 2px; }
.reveal-copy.copied { color: var(--ink); background: var(--teal-bright); border-color: var(--teal-bright); }

/* Explanatory copy for a control cluster, collapsed behind a small (i) button rather than
   always-visible (Casey, 08/26/26 — New Patient's "what these buttons do" line read as
   stray text; chose this over an always-visible tinted callout after seeing both). Sits
   inline in the button row, right next to Cancel — not pushed to the far right. Built on
   <details> like .user-menu/.sortmenu (no JS beyond menus.js's shared outside-click/
   Escape, CSP-safe). */
.info-pop { position: relative; display: inline-flex; align-self: center; }
.info-pop summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 999px; color: var(--ink-faint); background: var(--surface-2);
  transition: color .12s var(--ease-out-sm), background .12s var(--ease-out-sm);
}
.info-pop summary::-webkit-details-marker { display: none; }
.info-pop summary svg { width: 16px; height: 16px; stroke-width: 2; }
@media (hover: hover) { .info-pop summary:hover { color: var(--teal-deep); background: var(--teal-tint); } }
.info-pop[open] summary { color: var(--teal-deep); background: var(--teal-tint); }
.info-pop-panel {
  position: absolute; top: calc(100% + 8px); left: 0; z-index: 30;
  width: 320px; max-width: calc(100vw - 24px); padding: 12px 14px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-pop); font-size: 12.5px; line-height: 1.55; color: var(--ink-faint);
}

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

label, .lbl, .class-field legend {
  display: block;
  font-family: var(--f-display);
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--ink-faint);
  margin: 0 0 5px;
}
input[type=text], input[type=password], input[type=email], input[type=tel],
input[type=number], input[type=date], input[type=search], select, textarea {
  width: 100%;
  padding: 9px 12px;
  font: 400 14px/1.4 var(--f-body);
  color: var(--ink);
  background: var(--surface-2);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  transition: border-color .15s, box-shadow .15s;
}
input[type=date], input[type=number], .mono-input { font-family: var(--f-num); font-variant-numeric: tabular-nums; font-size: 13px; }
select { appearance: auto; }
/* display:block kills the inline-baseline descender gap — as an inline-block a textarea
   leaves ~6px under itself, which showed up as 21px of bottom padding on the Notes card
   where every other card had 15. */
textarea { resize: vertical; min-height: 74px; display: block; }
input:focus, select:focus, textarea:focus {
  outline: 0;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(0,130,126,.16);
}
input[disabled], select[disabled] { background: var(--well); color: var(--ink-faint); }
/* Unsaved / blocked fields (unsaved_guard.js, Casey 10/02/26): a changed field on a
   data-unsaved-mark form goes red with an UNSAVED tag on its label until it is saved or
   discarded; a required field that stopped a Save goes red the same way. Red, not
   amber, on Casey's call — it has to read as "this is not saved". */
.field.is-unsaved :is(input:not([type=checkbox]), select, textarea),
.field.is-invalid :is(input:not([type=checkbox]), select, textarea),
.field.is-unsaved :is(input, select, textarea):focus,
.field.is-invalid :is(input, select, textarea):focus {
  border-color: var(--crit);
  box-shadow: 0 0 0 3px rgba(192,57,43,.14);
}
.field.is-unsaved > label:first-child,
.field.is-invalid > label:first-child { color: var(--crit); }
.field.is-unsaved > label:first-child::after {
  content: "Unsaved";
  content: "Unsaved" / " (unsaved)"; /* the field's spoken name, not "ClassificationUnsaved" */
  margin-left: 7px; padding: 1px 7px; border-radius: 999px;
  font: 600 10px/1.5 var(--f-body); letter-spacing: .05em; text-transform: uppercase;
  vertical-align: 1px; color: #fff; background: var(--crit);
}
::placeholder { color: #a3b2b4; }

/* ---------- date fields ----------
   Everything here is the part of a date input we actually own: the box, its
   mm/dd/yyyy segments, and the calendar button. The panel that drops down is browser
   chrome — see the color-scheme/accent-color note in the tokens block for the only two
   levers on it. Chrome names these pseudo-elements ::-webkit-*; Firefox and Safari draw
   their own and simply ignore the rules, which is a graceful degrade, not a bug.
   global.js opens the picker on a click ANYWHERE in the field (Casey, 07/20/26), so the
   whole control has to read as clickable, not just the icon. */
input[type=date] { cursor: pointer; }
@media (hover: hover) {
  input[type=date]:hover { border-color: var(--line-strong); background: var(--well); }
}
input[type=date]:focus { background: var(--surface-2); }
/* Our own calendar glyph in place of Chrome's solid black one. Replacing the
   background-image is the technique Chrome honours here (mask is ignored — see the
   token comment), which is why the hover colour needs a second data URI rather than a
   colour swap. */
input[type=date]::-webkit-calendar-picker-indicator {
  width: 16px;
  height: 16px;
  padding: 0;
  margin-left: 8px;
  opacity: 1;
  cursor: pointer;
  background: var(--icon-calendar) center / 16px 16px no-repeat;
}
input[type=date]:focus::-webkit-calendar-picker-indicator { background-image: var(--icon-calendar-on); }
@media (hover: hover) {
  input[type=date]:hover::-webkit-calendar-picker-indicator { background-image: var(--icon-calendar-on); }
}
/* Segments: the / separators recede so the digits carry the field.
   What is NOT here, deliberately (tried and verified dead in Chrome, 08/06/26): the
   highlight on the segment you're currently typing in. It stays the browser's blue —
   `::-webkit-datetime-edit-*-field:focus { background }` and `::selection` are both
   ignored, because Chrome paints that with its internal selection colour. Same class of
   limit as the popup itself. Don't re-add those rules; they look right and do nothing. */
input[type=date]::-webkit-datetime-edit { padding: 0; }
input[type=date]::-webkit-datetime-edit-fields-wrapper { padding: 0; }
input[type=date]::-webkit-datetime-edit-text { color: var(--ink-faint); padding: 0 1px; }
/* An EMPTY date field shows only the calendar icon, not "mm/dd/yyyy" (Casey, 10/01/26:
   the segments read as "you have to type this out" when a click opens the calendar).
   global.js keeps .date-empty in step with the value, and adds .date-typing on the first
   digit/arrow key so someone typing a date still sees the slots fill in; it comes off on
   blur. Focus alone never shows them — the panel closing on a scroll left the field
   focused and showing mm/dd/yyyy, which is exactly what Casey didn't want.
   opacity on the webkit pseudo also hides Chrome's blue focused-segment box, which
   color:transparent alone leaves behind; color covers Firefox/Safari. */
input[type=date].date-empty:not(.date-typing) { color: transparent; }
input[type=date].date-empty:not(.date-typing)::-webkit-datetime-edit { opacity: 0; }

/* ---------- date picker panel (datepicker.js) ----------
   The 08/06 note above says the native calendar popup is browser chrome no selector can
   reach. This is the answer to that: our own panel, drawn in our own tokens, opened on a
   FINE POINTER ONLY. Touch still gets the native wheel — better than anything we would
   write, and it comes with the OS keyboard and a11y behaviour for free.

   position: fixed, and the element is appended to <body> by the script rather than next
   to its input. That is deliberate: .card keeps an identity transform after card-rise,
   and ANY transformed ancestor becomes the containing block for position:fixed — a panel
   rendered inside a card resolves top/left against the card, not the viewport (the
   08/06 name-sort menu landed 227px low before it was parked at 0,0 and measured). A
   body child has no transformed ancestor, so viewport coordinates are simply correct,
   and it escapes .tablewrap's overflow clipping in the same move. */
.dp {
  position: fixed;
  z-index: 60;
  width: 268px;
  padding: 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-pop);
  /* Popover tier: 150-250ms, transform+opacity only, out-curve (styling skill, Motion). */
  animation: dp-in .16s var(--ease-out-md);
}
@keyframes dp-in { from { opacity: 0; transform: translateY(-4px) scale(.98); } }

.dp-head { display: flex; align-items: center; gap: 4px; margin-bottom: 6px; }
.dp-title {
  flex: 1; text-align: center;
  font-family: var(--f-display); font-size: 13px; font-weight: 700; color: var(--ink);
}
.dp-nav {
  width: 28px; height: 28px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 8px; background: transparent;
  color: var(--ink-faint); cursor: pointer;
  transition: background .12s var(--ease-out-sm), color .12s var(--ease-out-sm);
}
.dp-nav svg { width: 18px; height: 18px; stroke-width: 2; }
@media (hover: hover) { .dp-nav:hover { background: var(--surface-2); color: var(--teal); } }
.dp-nav:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }

.dp-dow, .dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.dp-dow {
  margin-bottom: 4px;
  font-size: 10.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-faint); text-align: center;
}

.dp-day {
  height: 32px;
  border: 0; border-radius: 8px; background: transparent;
  font-family: var(--f-num); font-variant-numeric: tabular-nums;
  font-size: 13px; color: var(--ink); cursor: pointer;
  transition: background .12s var(--ease-out-sm), color .12s var(--ease-out-sm);
}
/* Leading/trailing days of the adjacent months stay visible but recede — the 6-week grid
   is a fixed 42 cells so the panel is one height all year and never resizes under the
   cursor while you step through months. */
.dp-day.adj { color: var(--ink-faint); opacity: .55; }
@media (hover: hover) { .dp-day:hover:not(:disabled) { background: var(--teal-tint); } }
.dp-day:disabled { opacity: .3; cursor: default; }
/* Today is outlined, the selection is filled — so "today" stays legible even when some
   other day is chosen, which a single highlight cannot do. */
.dp-day.today { box-shadow: inset 0 0 0 1px var(--line-strong); font-weight: 600; }
.dp-day.on { background: var(--grad-teal); color: #fff; font-weight: 700; box-shadow: none; }
.dp-day:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }

.dp-foot {
  display: flex; gap: 6px; justify-content: space-between;
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line);
}

.field { margin-bottom: 10px; }
/* A checkbox sharing a grid row with labelled inputs sits on the LABEL line by default,
   which reads as misaligned against the inputs beside it. Opt-in, not automatic: only
   the field that should drop to the input line asks for it.
   The 21px clears the sibling label exactly — its 16.3px line box plus the 5px margin
   under it — and then the .check box is given the controls' own 40px height so the two
   CENTRES line up. Padding alone only matched their tops, which still read as 10px high
   against a 40px select (Casey, 08/24/26: "the active checkbox looks terrible"). Same
   trick as .filter-panel-inner .field .check. */
.field.on-input-line { padding-top: 21px; }
.field.on-input-line > .check { height: 40px; }
.field .help { font-size: 12px; color: var(--ink-faint); margin-top: 4px; }
/* A select sharing its line with an .info-pop trigger (Casey, 08/26/26: Default date's
   help line collapsed behind an (i) button next to the field instead of printed below
   it) — the bare select rule sets width:100%, which would push the button off the field
   entirely, so it needs to shrink to make room instead. */
.field .row > select { width: auto; flex: 1 1 auto; min-width: 0; }
/* Same shrink treatment for a number input sharing a .row with that select — the bulk
   letters card stacks its label above the control (it shares a .grid row with the two
   date fields), so the offset's mode select and its day count sit side by side UNDER
   the label rather than inline beside it the way .field-inline does on the single-client
   screen (Casey, 09/05/26: stacked "looks bad"). Without this the input's base
   width:100% would claim the whole row and push the select out of it. */
.field .row > input[type="number"] { width: auto; flex: 1 1 auto; min-width: 0; }
/* Reserved single line so the form below never shifts when a code is picked
   (Casey, 07/20/26); long descriptions ellipsize, full text in the tooltip. */
#code-desc { min-height: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* A secondary field group INSIDE a card, set apart by a divider + small label rather than
   a nested card (Casey, 08/26/26: Responsible party + Contact merged into one card; Spouse
   needed its own visual section within it). Not a component to reach for lightly — most
   cards should stay one flat body; this exists for the one case where a card legitimately
   holds two people's info. */
.field-group { margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line); }
.field-group-label {
  font-family: var(--f-display); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em; color: var(--teal-deep);
  margin: 0 0 12px;
}
.grid { display: grid; gap: 14px 18px; }
/* .field's margin-bottom stacked on top of the grid's row gap, which put 24px between
   rows but also left the LAST row's 10px stranded against the card's 15px padding — so
   every card body read 15px above the first label and 25px below the last control
   (Casey, 08/24/26: "your vertical padding on the patient and RP cards is incorrect" —
   he caught it on the two shortest cards, where top and bottom are close enough together
   to compare by eye). The row rhythm was never the problem, so it is preserved exactly:
   the margin moves into the gap, 14 + 10 = 24, and only the trailing copy disappears.
   Outside a grid .field keeps its margin — stacked fields have no gap to space them. */
.grid > .field { margin-bottom: 0; }
.grid:has(> .field) { row-gap: 24px; }
/* The same 24px between ROWS that aren't in one grid: two field grids stacked (doctor
   form's Client/Prefix/Degree over First/M.I./Last/Suffix), a grid then a lone field, or
   two lone fields. Those used to get 0px or 10px — labels jammed against the control
   above (Casey, 09/30/26, on the doctor, office, user, patient, letters and report
   forms). Block siblings' margins collapse, so margin-top 24 over a 10px margin-bottom
   gives 24, not 34. */
.grid:has(> .field) + .grid:has(> .field),
.grid:has(> .field) + .field,
.field + .grid:has(> .field),
.field + .field { margin-top: 24px; }
/* ...but never between cells of ONE grid row — the grid's own gap spaces those. */
.grid > .field + .field { margin-top: 0; }
/* Whatever ends a card body, the card's padding owns the bottom edge — a trailing margin
   on the last child is always double-counted. */
.card-body > *:last-child { margin-bottom: 0; }
.grid.c2 { grid-template-columns: repeat(2, 1fr); }
.grid.c3 { grid-template-columns: repeat(3, 1fr); }
.grid.c4 { grid-template-columns: repeat(4, 1fr); }
/* A cell that fills the rest of its row from column 2 (patient form: Active beside
   Account #). 2 / -1 follows the grid's own column count — 4 across, or 2 under 900px —
   and the one-column phone grid below drops it back to an ordinary cell. */
.grid > .fill-row { grid-column: 2 / -1; }
/* cards placed side by side in a grid: the gap spaces them — the stacked-card
   margin would push the second card down and break top alignment */
.grid > .card + .card { margin-top: 0; }
/* two fields stacked in one grid cell (e.g. Type over Code on the activity form) */
.stack { display: flex; flex-direction: column; gap: 14px; }
/* Activity logging row: three short controls sized to their content, then the picked
   code's description filling the space they leave (Casey, 08/05/26 — "none of the options
   need that much space", and under the row it read as stranded). The description is a
   flex item on the same line as the selects, nudged down to sit against them. */
.logrow { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 14px 16px; }
.logrow > .field { width: 170px; margin: 0; }
/* The date field is as wide as its input plus the preview that sits beside it. */
.logrow > .field.date { width: auto; }
.dateline { display: flex; align-items: center; gap: 10px; }
.dateline input { width: 170px; }
.dateline .help { margin-top: 0; max-width: 240px; }
/* Tight under the controls: 6px, the same air a label leaves above its own field. */
#code-desc { margin: 6px 0 10px; font-size: 12px; color: var(--ink-faint); }
@media (max-width: 600px) { .logrow > .field, .logrow > .field.date { width: 100%; } }
/* A picked code's prompts (Due Date, Payment Amount…) take the same 170px columns and
   16px gap as Type/Code/Next call date above, so they line up under them instead of
   stretching to thirds of the card (Casey, 10/01/26). Still a .grid, so the 24px row
   rhythm around it is unchanged. */
#prompts { grid-template-columns: repeat(auto-fill, 170px); column-gap: 16px; }
/* ...and 24px under that row, the app's between-rows rhythm (labels sat flush on the
   selects before — nothing gave a .grid that follows .logrow any top margin). */
#prompts:has(> .field) { margin-top: 24px; }
/* Before a code is picked #prompts is empty, and the Note field under it had no top
   margin at all — its label sat ~8px under the selects (Casey, 10/01/26). */
#prompts + .field { margin-top: 24px; }
@media (max-width: 600px) { #prompts { grid-template-columns: 1fr; } }
@media (max-width: 900px) { .grid.c3, .grid.c4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .grid.c2, .grid.c3, .grid.c4 { grid-template-columns: 1fr; } .grid > .fill-row { grid-column: auto; } }

/* ---------- two-column form body ---------- */
/* The patient form's six cards stacked full width came to ~1290px of card, so on a
   laptop everything from Contact down sat below the fold while every c4 row left a
   lot of dead horizontal space (Casey, 08/24/26: "at full size there is too much
   going on below the fold ... no scrolling really needed").

   Assignment deliberately stays OUTSIDE this wrapper, full width: its four selects
   carry office names up to 15 characters, and a 597px column gives a c4 field 126px,
   which truncates them. The other five cards split left/right.

   1000px is the breakpoint Casey named, and it is above the 900px point where the
   inner .grid rows already collapse to two-up — so the narrow layout is untouched. */
.form-cols { display: grid; gap: 14px; grid-template-columns: 1fr; }
@media (min-width: 1000px) { .form-cols { grid-template-columns: 1fr 1fr; } }
/* The Edit patient modal hosts the same partial. dialog.wide is 1080px and its body
   already scrolls, so splitting there would only shrink each column below what the
   fields need. */
dialog .form-cols { grid-template-columns: 1fr; }

/* Container queries, not viewport queries: the same card body renders at 1208px on the
   page, 597px in a column, and 1008px in the modal, and only the card knows which. This
   is what lets the field grids below stay correct in all three without a template flag. */
.form-cols .card-body { container-type: inline-size; }
@container (max-width: 700px) {
  /* First / M.I. / Last / Suffix — two of the four hold three characters, so an even
     split wastes on them what the names need. Same for the responsible party's Prefix. */
  .grid.c4.names { grid-template-columns: 1fr 60px 1fr 82px; }
  .grid.c4.rp    { grid-template-columns: 92px 1fr 60px 1fr; }
  /* Contact's eight fields are ONE grid so the column count alone reflows them: four
     across on the page (the two rows it has always had), three across in a column. */
  .grid.c4.fit3  { grid-template-columns: repeat(3, 1fr); }
}
/* Three tall panels across — the admin user form's Scope lists (Casey, 08/19/26: three
   side by side "on full screen size"). Deliberately NOT .grid.c3, whose 900px breakpoint
   would put three ~250px office lists on Evelyn's 960 screen; each of these carries a
   search box and office names like "Hannon Orthodontics (Hannon)". .page caps content at
   1208px, so 1280 viewport is the first width where a column clears ~390px. Min-width
   queries here rather than max-, so the stacked single column is the floor. */
/* The panel header is `.spread`: a label, then a plain <span> holding All + None. That
   span is inline, so once the columns narrow the label wraps, the span gets squeezed and
   its two buttons stack — which throws all three column headers out of alignment (Casey's
   08/19/26 screenshot). Pin the button pair as an unshrinkable nowrap unit and give the
   label the remaining width; scoped to these panels rather than patching the shared
   .spread utility, which a dozen card heads also use. */
.grid.cols-3up > .field > .spread { flex-wrap: wrap; }
.grid.cols-3up > .field > .spread > span { flex: none; white-space: nowrap; }
.grid.cols-3up > .field > .spread > label { flex: 1 1 auto; min-width: 0; }
.grid.cols-3up { grid-template-columns: 1fr; }
@media (min-width: 760px)  { .grid.cols-3up { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1280px) { .grid.cols-3up { grid-template-columns: repeat(3, 1fr); } }
/* The panels are .field, whose bottom margin would break the row's baseline alignment. */
.grid.cols-3up > .field { margin-bottom: 0; }

/* A checkbox that qualifies the field above it (activity log's "Program Patient Note"
   under the Note) rather than standing on its own row. */
.note-flag-row { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
.note-flag-row .check.note-flag { margin: 0; }
.note-flag-row .info-pop summary { width: 22px; height: 22px; }
.check.note-flag { margin-top: 8px; }
.check { display: flex; align-items: center; gap: 8px; font: 400 13.5px/1.4 var(--f-body); text-transform: none; letter-spacing: 0; color: var(--ink); }
.check input { width: 16px; height: 16px; accent-color: var(--teal); margin: 0; }
/* A quiet switch under a field (patient form's "Show inactive reps", cascade.js). */
.check.check-sub { margin-top: 6px; font-size: 12.5px; color: var(--ink-soft); gap: 6px; }
.check.check-sub input { width: 14px; height: 14px; }
.checkgroup { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 6px 14px; }

/* Address autocomplete listbox (docs/SPEC-ADDRESS-LOOKUP.md, address_suggest.js) —
   reusable component, not specific to the patient form. Plain absolute positioning
   inside .addr-suggest (not JS-driven position:fixed): the Edit-patient modal's
   .card-body scrolls, and a transformed .card can become a fixed element's containing
   block (menus.js's sortmenu comment) — staying in normal flow relative to the field
   wrapper sidesteps that trap entirely, at the cost of the menu being clippable if the
   field is scrolled to the very bottom edge of a short viewport, an accepted trade-off. */
.addr-suggest { position: relative; }
.addr-suggest-menu {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0;
  max-height: 260px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 8px; box-shadow: var(--shadow-pop); z-index: 20; padding: 4px;
}
.addr-suggest-row {
  display: block; width: 100%; text-align: left; background: none; border: 0;
  padding: 8px 10px; margin: 0; border-radius: 6px; cursor: pointer;
  font: 400 13px/1.4 var(--f-body); color: var(--ink);
}
.addr-suggest-row.active { background: var(--teal-tint); }
@media (hover: hover) {
  .addr-suggest-row:hover { background: var(--teal-tint); }
}
.addr-suggest-row:focus-visible { outline: 2px solid var(--teal-bright); outline-offset: -2px; }
.addr-suggest-none { padding: 10px 12px; font-size: 12.5px; color: var(--ink-faint); }
.addr-suggest-attrib {
  padding: 6px 10px 4px; margin-top: 2px; border-top: 1px solid var(--line);
  font-size: 10.5px; color: var(--ink-faint); text-align: right;
}

/* Searchable/scrollable checkbox group (admin user form's Scope card) — the
   same .opt-search/.opt-list/.opt-nomatch components the Workloads filter
   drawer uses (app.css "filters drawer" section), boxed here since there's no
   drawer chrome to bound them visually on a plain form. */
.checkgroup-picker { border: 1px solid var(--line); border-radius: var(--radius); padding: 8px 10px; background: var(--surface); }
.checkgroup-picker .opt-search { margin-bottom: 6px; }
.checkgroup-picker .opt-nomatch { padding: 4px 2px; }

/* ---------- code picker (partial_code_picker.html, code_picker.js) ----------
   Replaces the plain per-type checkbox card that used to be copied between the client
   and office forms (Casey, 09/17/26 — "a mess" at 66 codes across 10 types). Ten
   collapsed <details> groups (native, so it works with JS off), a live search that
   filters by code or description, and dense selectable tiles instead of bare checkbox
   rows. Every <input> keeps its original name/value — this is presentation only. */
.code-picker-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.code-picker-toolbar .opt-search { flex: 1 1 220px; min-width: 0; }
.code-picker-toolbar .opt-search input { width: 100%; }
.code-picker-actions { display: flex; gap: 8px; margin-left: auto; }
.code-picker-summary {
  font: 600 12px var(--f-body); color: var(--teal-deep); background: var(--teal-tint);
  border: 1px solid #bfe0dd; border-radius: 999px; padding: 4px 12px; white-space: nowrap;
}
.code-picker-nomatch { padding: 10px 2px; font-size: 13px; color: var(--ink-faint); }
.code-picker-nomatch[hidden] { display: none; }

.code-group { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.code-group + .code-group { margin-top: 8px; }
.code-group[hidden] { display: none; }
.code-group-summary {
  display: flex; align-items: center; gap: 10px; padding: 11px 14px; cursor: pointer;
  list-style: none; user-select: none;
}
.code-group-summary::-webkit-details-marker { display: none; }
.code-group-toggle { width: 16px; height: 16px; accent-color: var(--teal); margin: 0; flex: none; }
.code-group-name { font: 600 13.5px var(--f-body); color: var(--ink); }
.code-group-count {
  margin-left: auto; font-variant-numeric: tabular-nums; font-family: var(--f-mono);
}
.code-group-chevron {
  flex: none; width: 16px; height: 16px; stroke-width: 2.2; color: var(--ink-faint);
  transition: transform .15s var(--ease-out-sm);
}
.code-group[open] > .code-group-summary .code-group-chevron { transform: rotate(90deg); }
@media (hover: hover) {
  .code-group-summary:hover .code-group-name { color: var(--teal-deep); }
}

.code-tiles {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 8px; padding: 0 14px 14px;
}
.code-tile {
  position: relative; display: block; min-width: 0; padding: 9px 12px; border-radius: 8px;
  border: 1px solid var(--line-strong); background: var(--surface-2); cursor: pointer;
  text-transform: none; letter-spacing: 0; /* tiles sit inside .field, whose labels are caps */
  transition: background-color .12s var(--ease-out-sm), border-color .12s var(--ease-out-sm);
}
.code-tile[hidden] { display: none; }
/* the real checkbox stays in the tab order and reachable by a screen reader —
   visually hidden, never display:none (CLAUDE.md styling skill: focus must survive). */
.code-tile input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.code-tile-body { display: block; }
.code-tile-code { display: block; font: 600 13px var(--f-mono); color: var(--ink); }
.code-tile-desc {
  display: block; margin-top: 2px; font-size: 12px; color: var(--ink-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.code-tile:has(input:checked) {
  background: var(--teal-tint); border-color: var(--teal-bright);
}
.code-tile:has(input:checked) .code-tile-code { color: var(--teal-deep); }
.code-tile:has(input:checked)::after {
  content: ""; position: absolute; top: 8px; right: 8px; width: 14px; height: 14px;
  border-radius: 999px; background: var(--teal);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E") center/70% no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E") center/70% no-repeat;
}
.code-tile:has(input:focus-visible) { outline: 2px solid var(--teal-bright); outline-offset: 2px; }
@media (hover: hover) {
  .code-tile:hover { border-color: var(--teal-bright); }
}
@media (prefers-reduced-motion: reduce) {
  .code-group-chevron, .code-tile { transition: none; }
}
@media (max-width: 480px) {
  .code-tiles { grid-template-columns: 1fr; }
  .code-picker-actions { margin-left: 0; }
}

.form-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-top: 16px;
  margin-top: 6px;
  border-top: 1px solid var(--line);
}
/* a form-actions row with nothing above it in the card — Access controls'
   buttons, or a checkbox-picker's Check-all/Uncheck-all toolbar right under
   the card head — so there's no divider to draw. (.mt0 alone only zeroed the
   margin, not the border, which is why those rows still showed a stray line.)
   The real form-actions divider at the bottom of a form is untouched. */
.form-actions.plain { border-top: 0; padding-top: 0; margin-top: 0; }
/* pushes a lone action to the right edge — the patient edit page's Delete, kept far
   from Save changes on purpose (Casey, 08/05/26) */
.form-actions.right { justify-content: flex-end; }
/* The Access controls buttons share a half-width card, so they must be free to wrap
   rather than push it wide at Casey's 960 focal width. */
.form-actions.plain { flex-wrap: wrap; }
/* Opt-in for any other crowded row (Casey, 08/26/26: New Patient's five items — three
   buttons, Cancel, the new info-pop — ran off the right edge of a 375px screen instead
   of wrapping, since the base rule stays nowrap so ordinary two/three-button rows never
   wrap mid-word around a stray element). Scoped per-row rather than added to the base
   rule so it doesn't touch every already-shipped .form-actions in the app unreviewed. */
.form-actions.wrap { flex-wrap: wrap; row-gap: 10px; }
/* Drops just the divider rule, keeping the row's own top spacing — for a row that sits
   right under another .form-actions rather than under a card's fields (Casey, 08/26/26:
   New Patient's two stacked action rows read as one grey line blending into the row
   above it and a second redundant one above that). `.plain` zeroes the spacing too,
   which isn't wanted here — the gap between sections should stay, only the rule goes. */
.form-actions.no-line { border-top: 0; }

/* Generate toolbar (letters report): action buttons left, offset field pinned right,
   one row — no top divider since it's the only thing in the card body. */
.generate-row { display: flex; align-items: center; flex-wrap: wrap; gap: 14px 18px; }
.generate-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.generate-offset { margin-left: auto; }
.generate-offset input { width: 130px; }
.card-head .cols-btn { margin: -6px 0 -6px auto; align-self: center; flex-shrink: 0; height: 34px; } /* sits in the bar without growing it */
/* Include-filter preview: rows the current Doctor/Rep selection would leave out of
   the generated output (letters.js toggles this; the server does the real filtering). */
.ledger tr.signed-out td { opacity: 0.35; }

/* ---------- buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 9px 18px;
  font: 600 13px/1 var(--f-display);
  letter-spacing: .02em;
  color: #fff;
  background: var(--teal);
  border: 1px solid var(--teal);
  border-radius: 8px;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background .15s, border-color .15s, box-shadow .15s, color .15s, transform .05s;
}
@media (hover: hover) {
  .btn:hover { background: var(--teal-deep); border-color: var(--teal-deep); text-decoration: none; }
}
.btn:active { transform: translateY(1px); }
/* the primary (teal) button carries the gradient and a tactile lift; secondary,
   danger and the AC-blue page action keep their flat treatments */
.btn:not(.secondary):not(.danger):not(.blue) {
  background: var(--grad-teal); border-color: transparent;
  box-shadow: 0 2px 10px rgba(0,130,126,.28);
  transition: transform .2s var(--ease-out-sm), box-shadow .2s var(--ease-out-sm), filter .2s var(--ease-out-sm);
}
@media (hover: hover) {
  .btn:not(.secondary):not(.danger):not(.blue):hover {
    filter: brightness(1.06); transform: translateY(-1px);
    box-shadow: 0 6px 18px rgba(0,130,126,.38);
  }
}
.btn:not(.secondary):not(.danger):not(.blue):active { transform: scale(.98); }
/* .btn.back — the "← Offices" way out of a record. Deliberately NOT .secondary: it takes
   the primary look above so it is as easy to find as the list's "New …" button (Casey,
   09/27/26: the grey one was "hard to find"). The class is only a name for the sweep;
   paging buttons (← Newer / ← Prev) stay .secondary. */
.btn.secondary { background: transparent; color: var(--ink-soft); border-color: var(--line-strong); box-shadow: none; }
.btn.blue { background: var(--blue); border-color: var(--blue); }
.btn.danger { background: transparent; color: var(--crit); border-color: var(--crit); box-shadow: none; }
@media (hover: hover) {
  .btn.secondary:hover { background: var(--surface-2); color: var(--ink); }
  .btn.blue:hover { background: var(--blue-deep); border-color: var(--blue-deep); }
  .btn.danger:hover { background: var(--crit); color: #fff; }
}
.btn.small { padding: 6px 11px; font-size: 12px; }
.btn.icon-btn { padding: 5px 8px; line-height: 0; vertical-align: middle; }
.btn.icon-btn svg { width: 14px; height: 14px; stroke-width: 1.8; }
.btn.wide { width: 100%; }
.btn[disabled] { opacity: .5; cursor: not-allowed; }
.btn svg { width: 16px; height: 16px; stroke-width: 2.2; vertical-align: middle; }
.btn svg.ext-icon { width: 13px; height: 13px; stroke-width: 2; }
.linklike { background: none; border: 0; padding: 0; font: inherit; color: var(--teal); cursor: pointer; }
@media (hover: hover) {
  .linklike:hover { text-decoration: underline; }
}

/* keyboard-focus ring for controls whose own focus treatment isn't otherwise
   handled (form fields have their own — see the input:focus rule below) */
.btn:focus-visible, .filters-toggle:focus-visible, .cols-btn:focus-visible,
table.ledger td.actions a:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

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

.tablewrap { overflow-x: auto; }
/* .card-body.tight sets overflow:hidden (for the rounded-corner clip) with higher
   specificity than .tablewrap alone, which was silently clipping wide tables (e.g.
   the 12-col activity-by-type report) instead of letting them scroll. Re-assert
   horizontal scroll here while keeping the vertical clip. */
.card-body.tight.tablewrap { overflow-x: auto; overflow-y: hidden; }
/* results are being fetched for a filter tick (filterbar.js applyInPlace) — a hint that
   the list is refreshing, not a spinner: opacity only, well under the 300ms UI ceiling */
.tablewrap.is-updating { opacity: .55; }
.tablewrap { transition: opacity .12s var(--ease-out-sm); }
table.ledger { width: 100%; border-collapse: collapse; background: var(--surface); font-size: 14.5px; }
table.ledger th {
  font-family: var(--f-display);
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-faint);
  text-align: left;
  padding: 13px 16px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
/* right-aligns over td.num's right-aligned numbers; th a's inline-flex box
   still shifts as a unit, so a sortable num header (icon+label+arrow) lines
   up the same way a plain text one does */
table.ledger th.num { text-align: right; }
table.ledger th a { color: inherit; display: inline-flex; align-items: center; gap: 5px; }
@media (hover: hover) {
  table.ledger th a:hover { color: var(--teal); text-decoration: none; }
}
table.ledger th a.sorted { color: var(--teal); }
/* in-browser sort (tables.js data-client-sort) — a button, styled like the th links */
table.ledger th .th-sort {
  all: unset; cursor: pointer; display: inline-flex; align-items: center; gap: 5px;
  color: inherit; font: inherit; letter-spacing: inherit; text-transform: inherit;
}
table.ledger th .th-sort:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; border-radius: 3px; }
@media (hover: hover) {
  table.ledger th .th-sort:hover { color: var(--teal); }
}
table.ledger th .th-sort.sorted { color: var(--teal); }
table.ledger th .dir { color: var(--teal); font-size: 9px; }

/* Patient header: one column, two possible name sorts. The link toggles direction on
   whichever key is active; this chevron opens the picker (last vs first). Built on
   <details> like .user-menu — same no-JS, CSP-safe popover pattern. */
.th-namesort { display: inline-flex; align-items: center; gap: 3px; }
.sortmenu { position: relative; display: inline-flex; }
.sortmenu summary { list-style: none; cursor: pointer; display: flex; align-items: center;
  padding: 2px; border-radius: 5px; color: var(--ink-faint); transition: color .12s, background .12s; }
.sortmenu summary::-webkit-details-marker { display: none; }
.sortmenu summary svg { width: 13px; height: 13px; stroke-width: 2.4; transition: transform .15s; }
@media (hover: hover) {
  .sortmenu summary:hover { color: var(--teal); background: var(--teal-tint); }
}
.sortmenu[open] summary { color: var(--teal); }
.sortmenu[open] summary svg { transform: rotate(180deg); }
/* the th sets uppercase/nowrap/faint-ink on everything inside it — undo all of it here */
.sortmenu-panel {
  position: absolute; top: calc(100% + 8px); left: -8px; z-index: 30;
  min-width: 168px; padding: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-pop);
  font-family: var(--f-body); text-transform: none; letter-spacing: normal;
}
.sortmenu-head {
  display: block; padding: 5px 9px 6px;
  font: 600 9.5px var(--f-display); text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-faint);
}
/* `table.ledger th a` sets inline-flex and would win on specificity, which laid the two
   items out side by side — qualify these the same way so they stack. */
table.ledger th a.sortmenu-item {
  display: flex; align-items: center; gap: 7px;
  padding: 7px 9px; border-radius: 6px;
  font: 500 13.5px var(--f-body); color: var(--ink); text-decoration: none;
}
@media (hover: hover) {
  table.ledger th a.sortmenu-item:hover { background: var(--teal-tint); color: var(--teal-deep); }
}
.sortmenu-item .tick { visibility: hidden; color: var(--teal); font-size: 11px; width: 9px; }
table.ledger th a.sortmenu-item.on { color: var(--teal-deep); }
.sortmenu-item.on .tick { visibility: visible; }
/* The panel lives inside the table's scroll box, which clips it (a one-row result cut
   the menu off) and can't be unclipped without killing the horizontal scroll a phone
   needs. menus.js therefore switches it to position:fixed on open and pins it under the
   chevron, clamped to the viewport — these values are the un-scripted fallback. */
table.ledger td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
table.ledger tbody tr { transition: background .12s; }
@media (hover: hover) {
  table.ledger tbody tr:hover td { background: var(--surface-2); }
}
table.ledger tbody tr:last-child td { border-bottom: 0; }
/* A save that redirects to #log-N (note edits) lands that row mid-screen, not under the
   sticky app bar, and fades a teal wash out of it so you see which entry changed. */
table.ledger tbody tr:target { scroll-margin-top: 30vh; }
table.ledger tbody tr:target td { animation: row-landed 2.2s var(--ease-out-sm); }
@keyframes row-landed { from { background: var(--teal-tint); } to { background: transparent; } }
@media (prefers-reduced-motion: reduce) {
  table.ledger tbody tr:target td { animation: none; }
}
table.ledger td.mono, .mono { font-family: var(--f-num); font-variant-numeric: tabular-nums; font-size: 13px; }
table.ledger td.num { font-family: var(--f-num); font-variant-numeric: tabular-nums; font-size: 13px; text-align: right; }
table.ledger td.actions { text-align: right; white-space: nowrap; }
table.ledger td.actions a {
  font-weight: 600;
  font-size: 12.5px;
  color: var(--teal-deep);
  padding: 4px 10px;
  border-radius: 6px;
  transition: background .12s, color .12s;
}
@media (hover: hover) {
  table.ledger td.actions a:hover {
    background: var(--teal-tint);
    color: var(--teal-deep);
    text-decoration: none;
  }
}
table.ledger .empty td {
  text-align: center;
  color: var(--ink-faint);
  padding: 30px 16px;
  font-style: italic;
}
table.ledger tr.inactive-row td { color: var(--ink-faint); }

/* patient/queue niceties */
.tnum { font-variant-numeric: tabular-nums; }
.pt-name { font-weight: 600; color: var(--ink); text-decoration: none; }
.pt-name .first { font-weight: 400; color: var(--ink-soft); }
.atid { color: var(--teal); text-decoration: none; font-weight: 500; }
@media (hover: hover) {
  .pt-name:hover { color: var(--teal); }
  .atid:hover { text-decoration: underline; }
}
.noclass { color: var(--ink-faint); letter-spacing: .06em; } /* --- for unclassified */

/* overdue = tinted row + red date + left stripe */
table.ledger tr.overdue td { background: color-mix(in srgb, var(--crit-tint) 50%, transparent); }
@media (hover: hover) {
  table.ledger tr.overdue:hover td { background: var(--crit-tint); }
}
table.ledger tr.overdue td:first-child { box-shadow: inset 3px 0 0 var(--crit); }
table.ledger tr.overdue .date { color: var(--crit); font-weight: 600; }

/* per-column hide (Columns manager; workloads: Next call + Patient stay locked) */
table.hide-atid   .col-atid   { display: none; }
table.hide-client .col-client { display: none; }
table.hide-office .col-office { display: none; }
table.hide-doctor .col-doctor { display: none; }
table.hide-rep    .col-rep    { display: none; }
table.hide-class  .col-class  { display: none; }
table.hide-account .col-account { display: none; }
table.hide-nextcall .col-nextcall { display: none; }
table.hide-status .col-status { display: none; }
table.hide-logged .col-logged { display: none; }
table.hide-code .col-code { display: none; }
table.hide-rp .col-rp { display: none; }
table.hide-address .col-address { display: none; }

/* ---------- pills / stamps ---------- */

.pill {
  display: inline-block;
  font-family: var(--f-display);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid;
}
.pill.on  { color: #fff; border-color: transparent; background: var(--grad-teal); box-shadow: 0 2px 8px rgba(0,130,126,.32); }
/* in a dense list the glow reads as smudge — keep the gradient, drop the shadow */
table.ledger .pill.on { box-shadow: none; }
.pill.off { color: var(--ink-faint); border-color: var(--line-strong); background: var(--surface-2); }
.pill.warn { color: var(--crit); border-color: #ecc8c2; background: var(--crit-tint); }
.pill.class { color: var(--blue); border-color: #c5d4e6; background: #e9eff7; }
/* classification in a ledger column: same chip shape as Status, deliberately neutral —
   the teal/grey Status pill is the row's signal, the class shouldn't compete with it. */
.pill.neutral { color: var(--ink-soft); border-color: #d3dcdd; background: #eef2f2; }
/* Amber "worth a look", between .neutral (invisible) and .warn (danger red). For facts
   that are notable but not wrong — the admin user page's out-of-scope office assignments,
   which are inherited v2 reality, not an error to fix. Finally spends --warn-tint, which
   had sat in :root unused since the palette was set. */
.pill.note { color: var(--warn-ink); border-color: #edd9ae; background: var(--warn-tint); }

/* Casey, NOTES 08/19/26: "Active chip can just be grey like the others, doesn't need to
   be so obvious." Active is the norm on the Patients ledger, so a teal gradient on nearly
   every row was pure noise. Scoped to td.col-status (only the Patients list has one) so
   the teal .pill.on keeps its emphasis where it IS the exception — 2FA Enrolled, This
   device, the report_form Yes/No pairs. Inactive stays the fainter grey of the two and
   its row is already dimmed by tr.inactive-row, so the two states still read apart. */
table.ledger td.col-status .pill.on {
  color: var(--ink-soft); border-color: #d3dcdd; background: #eef2f2; box-shadow: none;
}

/* activity/audit codes (LS2, PR/CFM, SU…) — on the body font like the rest of the text;
   Casey, 09/29/26: the mono read "too robotic or typewriter looking". */
.code-chip {
  font-family: var(--f-body);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--teal-deep);
  background: var(--teal-tint);
  border: 1px solid #b8d9d6;
  border-radius: 999px;
  padding: 2px 10px;
  white-space: nowrap;
}

/* ---------- filter bar ---------- */

.filterbar {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  margin-bottom: 18px;
}
/* legacy simple rows (screens not yet ported to the collapsible bar) */
.filterbar .row { display: flex; flex-wrap: wrap; gap: 12px 16px; align-items: flex-end; padding: 12px 14px; }
.filterbar .row + .row { padding-top: 0; }
.filterbar .f { min-width: 150px; flex: 1; max-width: 230px; }
.filterbar .f.grow { max-width: none; }

/* primary row of the collapsible bar */
.filter-primary { display: flex; align-items: center; gap: 12px; padding: 12px 14px; flex-wrap: wrap; }
.field-inline { display: flex; align-items: center; gap: 9px; }
.field-inline > label { margin: 0; white-space: nowrap; }
/* a plain scoping select living directly in the primary row (e.g. admin/codes' Type
   select) — sized like the panel's fields; .due-within below overrides for its own case */
.field-inline select { width: auto; height: 40px; min-width: 140px; padding: 8px 11px; font: 500 13.5px var(--f-body); }
/* a prominent action living in the primary row (e.g. Patients' New patient) matches the 40px control height */
.filter-primary > .btn { height: 40px; }
.filter-primary > .btn svg { width: 17px; height: 17px; stroke-width: 2; }

/* Days to Show — the PRIMARY quick preset: Today / Tomorrow / Show All */
.showdd { position: relative; }
.showdd-btn {
  font-family: var(--f-display); font-weight: 700; font-size: 13px; letter-spacing: .02em;
  color: #fff; background: var(--blue); border: 0; border-radius: 8px;
  height: 40px; padding: 0 12px 0 13px; cursor: pointer; display: inline-flex; align-items: center; gap: 9px;
  box-shadow: var(--shadow-sm); transition: background .15s, opacity .15s; min-width: 150px;
}
@media (hover: hover) {
  .showdd-btn:hover { background: var(--blue-deep); }
}
/* muted when the numeric Show-days override is active */
.filterbar.within-active .showdd-btn { opacity: .5; }
.showdd-btn .cal { width: 16px; height: 16px; stroke-width: 2; opacity: .92; flex: none; }
.showdd-btn .lbl { flex: 1; text-align: left; margin: 0; font: inherit; color: inherit; text-transform: none; letter-spacing: inherit; }
.showdd-btn .chev { width: 14px; height: 14px; stroke-width: 2.4; transition: transform .18s; flex: none; }
.showdd.open .showdd-btn .chev { transform: rotate(180deg); }
.showdd-menu {
  position: absolute; top: calc(100% + 6px); left: 0; min-width: 100%;
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: var(--shadow-pop); padding: 6px; z-index: 30;
  opacity: 0; transform: translateY(-6px); pointer-events: none; transition: opacity .15s, transform .15s;
}
.showdd.open .showdd-menu { opacity: 1; transform: none; pointer-events: auto; }
.showdd-menu button {
  display: block; width: 100%; text-align: left; font: 500 14px var(--f-body); color: var(--ink);
  background: none; border: 0; border-radius: 7px; padding: 8px 14px; cursor: pointer; white-space: nowrap;
}
@media (hover: hover) {
  .showdd-menu button:hover { background: var(--teal-tint); color: var(--teal-deep); }
}
.showdd-menu button[aria-selected="true"] { color: var(--teal-deep); font-weight: 700; background: var(--teal-tint); }

/* Show days: N (1–30) — replaces the old calendar from/through range */
/* the range is 2–30 (v2's), so this never holds more than two digits — 104px was
   sized for content that cannot occur, and the slack cost the bar a row at 960px */
.due-within select {
  width: auto; font: 500 13.5px var(--f-body); height: 40px; min-width: 72px; padding: 8px 11px;
}
.filterbar.within-active .due-within select { border-color: var(--teal); background: var(--teal-tint); color: var(--teal-deep); font-weight: 700; }

/* live search */
/* The search is the bar's flexible element: a fixed width made the row overflow at
   ~960px (Evelyn's half-of-1920 working width) and wrapped Columns onto a second line.
   A small basis keeps every control on one row; grow takes back the slack up to the
   320px this used to be pinned at. */
.filter-search { position: relative; display: flex; align-items: center; flex: 1 1 190px; max-width: 320px; }
.filter-search svg { position: absolute; left: 12px; width: 16px; height: 16px; color: var(--ink-faint); pointer-events: none; }
.filter-search input { height: 40px; padding-left: 36px; }

/* Filters toggle (opens the scoping panel); Columns button shares the look */
.filters-toggle, .cols-btn {
  font-family: var(--f-display); font-weight: 600; font-size: 13px; color: var(--ink);
  background: var(--surface-2); border: 1px solid var(--line-strong); border-radius: 8px;
  height: 40px; padding: 0 13px; cursor: pointer; display: inline-flex; align-items: center; gap: 9px;
  transition: border-color .15s, color .15s, background .15s;
}
/* Icon-only (Casey, 08/17): the words cost ~150px in a row that has to survive 960px.
   The label stays in the DOM for screen readers and the title gives it back on hover —
   an aria-label alone would leave a sighted user with no way to name the button. */
.filters-toggle .lbl, .cols-btn .lbl {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.filters-toggle, .cols-btn { padding: 0 11px; gap: 6px; }
.filters-toggle { margin-left: auto; } /* pin Filters + Columns to the right of the bar */
@media (hover: hover) {
  .filters-toggle:hover, .cols-btn:hover { border-color: var(--teal); color: var(--teal-deep); background: var(--teal-tint); }
}
.filters-toggle .sliders, .cols-btn svg { width: 16px; height: 16px; stroke-width: 1.9; }
.filters-toggle .badge {
  min-width: 19px; height: 19px; padding: 0 5px; border-radius: 999px; background: var(--teal); color: #fff;
  font-size: 11px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center;
}
.filters-toggle .badge[hidden] { display: none; }

/* collapsible scoping panel — grid-rows 0fr→1fr animates auto height */
.filter-panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .28s var(--ease-out-md); }
.filterbar.open .filter-panel { grid-template-rows: 1fr; }
.filter-panel > .clip { overflow: hidden; }
.filter-panel-inner {
  border-top: 1px solid var(--line); padding: 15px 14px;
  display: flex; gap: 12px 14px; flex-wrap: wrap; align-items: flex-end;
}
.filter-panel-inner .field { margin: 0; display: flex; flex-direction: column; gap: 5px; }
.filter-panel-inner .field > label { margin: 0; }
.filter-panel-inner .field select { width: auto; height: 40px; min-width: 152px; }
.filter-panel-inner .field .check { height: 40px; } /* baseline-align with the 40px selects */
.panel-actions { display: flex; gap: 8px; margin-left: auto; align-items: flex-end; }

/* Classification multi-select (Workloads): a fieldset standing in for a .field,
   legend matching the plain label look, checkboxes wrapping in a compact row. */
.class-field { border: 0; padding: 0; margin: 0; min-width: 0; }
.class-field legend { padding: 0; float: none; width: auto; margin: 0 0 5px; }
.class-opts { display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: center; height: 40px; }
.class-opts label {
  display: flex; align-items: center; gap: 6px; font: 400 13.5px/1.4 var(--f-body);
  text-transform: none; letter-spacing: 0; color: var(--ink); white-space: nowrap; margin: 0;
}
.class-opts input { width: 16px; height: 16px; accent-color: var(--teal); margin: 0; }

/* active-filter chips — pinned at the bottom of the bar so a new chip never shifts the controls */
.filter-chips { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 14px; border-top: 1px solid var(--line); }
.filter-chips:empty { display: none; }
.chip {
  display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 500;
  background: var(--teal-tint); color: var(--teal-deep); border-radius: 999px; padding: 5px 6px 5px 12px;
}
.chip .k { color: var(--ink-faint); font-weight: 600; text-transform: uppercase; font-size: 10px; letter-spacing: .05em; }
.chip .x {
  width: 18px; height: 18px; border-radius: 999px; border: 0; background: rgba(0,130,126,.16); color: var(--teal-deep);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center; font-size: 14px; line-height: 1;
}
@media (hover: hover) {
  .chip .x:hover { background: rgba(0,130,126,.32); }
}

/* Activity page: "logged" confirmation beside Save activity/Clear, in place of the
   page-top .flash banner (Casey, 08/26/26 — on the form, not pushing anything down).
   A .chip rather than .flash: inline with the buttons on their own line, not a
   full-width block. No close button, so the right padding is symmetric with the left. */
.chip.activity-confirm { padding: 5px 14px 5px 12px; animation: chip-in .16s var(--ease-out-sm); }
.chip.activity-confirm svg { width: 15px; height: 15px; flex: none; }
@keyframes chip-in { from { opacity: 0; transform: scale(.92); } }

/* ---------- dialogs ---------- */

dialog {
  border: 0;
  border-radius: 14px;
  box-shadow: var(--shadow-pop);
  padding: 0;
  width: min(560px, 92vw);
  background: var(--surface);
  color: var(--ink);
}
dialog::backdrop { background: rgba(11,40,42,.40); }
/* Direct children only. The Edit patient modal nests real .card sections inside the
   dialog's body; without the child combinator every nested card head went sticky and
   every nested card body got its own 60vh scroller. */
dialog > .card-head { position: sticky; top: 0; background: var(--surface); z-index: 1; }
dialog > .card-body { max-height: 60vh; overflow-y: auto; }

/* A form-sized dialog: the patient form is a four-column grid, and at the default 560px
   every row would stack. Wide enough for c4 to survive, still short of the viewport. */
dialog.wide { width: min(1080px, 94vw); }
dialog.wide > .card-body { max-height: 78vh; }
/* Cards nested in a dialog are grouping, not floating objects — drop the elevation so
   the modal reads as one surface instead of cards stacked on a card. */
dialog .card { box-shadow: none; }
.note-entry { padding: 8px 0; border-bottom: 1px solid var(--line); font-size: 13.5px; }
.note-entry .meta { font-family: var(--f-num); font-variant-numeric: tabular-nums; font-size: 11px; color: var(--ink-faint); margin-bottom: 2px; }
.note-entry:last-child { border-bottom: 0; }
.note-entry.deleted { opacity: .55; }
.note-entry.deleted > div:nth-child(2) { text-decoration: line-through; }
.note-actions { display: flex; gap: 6px; margin-top: 6px; }
.head-actions { display: inline-flex; gap: 6px; align-items: center; }
.note-list-head { margin: 18px 0 2px; font: 700 11px var(--f-body); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-faint); }
#note-form + #note-list { margin-top: 0; }
.note-entry .note-body { white-space: pre-wrap; overflow-wrap: anywhere; }
.ver-tag {
  display: inline-block; margin-right: 6px; padding: 0 6px; border-radius: 99px;
  background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--line-strong);
  font: 700 10px/16px var(--f-body); letter-spacing: .03em; text-transform: uppercase; color: var(--ink-soft);
}
.ver-tag.current { background: var(--teal-tint); box-shadow: none; color: var(--teal-deep); }
/* Word diff between note versions: removed words struck through on a soft red wash, added
   words on a soft green wash with no underline. Color is not the only cue (strike-through
   vs none), and both inks stay dark enough for AA on their tints. */
.note-body del { background: var(--crit-tint); color: var(--crit); text-decoration: line-through; border-radius: 3px; padding: 0 1px; }
.note-body ins { background: var(--ok-tint); color: var(--ok-ink); text-decoration: none; border-radius: 3px; padding: 0 1px; }

/* "(edited)" tag after a note in the activity log: quiet until hovered or focused. The
   tooltip (data-tip, same pattern as .who-dot) opens ABOVE so the table's scroll wrapper
   never clips it on the last row — on the first row it lands over the header. */
.note-edited {
  position: relative; display: inline; background: none; border: 0; padding: 0; margin: 0;
  font: inherit; font-size: .88em; color: var(--ink-soft); cursor: pointer;
}
.note-edited:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; border-radius: 3px; }
.note-edited::after {
  content: attr(data-tip);
  position: absolute; left: 0; bottom: calc(100% + 6px); z-index: 5;
  width: max-content; max-width: 240px; white-space: normal; text-align: left;
  transform: translateY(3px); opacity: 0; pointer-events: none;
  padding: 5px 9px; border-radius: 7px;
  background: var(--ink); color: #fff;
  font: 600 12px/1.35 var(--f-body, inherit); letter-spacing: 0; text-decoration: none;
  box-shadow: 0 4px 14px rgba(11,40,42,.18);
  transition: opacity .12s ease, transform .12s ease;
}
.note-edited:focus::after { opacity: 1; transform: none; }
@media (hover: hover) {
  .note-edited:hover { color: var(--teal-deep); text-decoration: underline; text-underline-offset: 2px; }
  .note-edited:hover::after { opacity: 1; transform: none; }
}
.note-edited:focus-visible { text-decoration: underline; text-underline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .note-edited::after { transition: none; } }

/* "Patient info changed" details affordance (activity log) + its shared modal. The
   dotted underline + small info glyph mark the Details cell as clickable — v2 only
   showed the raw run-on sentence with no way to get here except via edit. */
.details-link {
  display: inline-flex; align-items: center; gap: 4px;
  background: none; border: 0; padding: 0; margin: 0; text-align: left;
  font: inherit; color: var(--ink); cursor: pointer;
  text-decoration: underline dotted var(--line-strong); text-underline-offset: 3px;
}
.details-link svg { width: 13px; height: 13px; stroke-width: 2; color: var(--ink-faint); flex: none; }
@media (hover: hover) {
  .details-link:hover { color: var(--teal-deep); text-decoration-color: var(--teal); }
  .details-link:hover svg { color: var(--teal); }
}
table.changes-table { width: 100%; border-collapse: collapse; font-size: 13.5px; margin-bottom: 10px; }
table.changes-table th {
  text-align: left; font: 600 10.5px var(--f-body); text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-faint); padding: 6px 10px; border-bottom: 1px solid var(--line);
}
table.changes-table td { padding: 7px 10px; border-bottom: 1px solid var(--line); vertical-align: top; word-break: break-word; }
table.changes-table tr:last-child td { border-bottom: 0; }
.changes-raw { font-size: 13.5px; white-space: pre-wrap; }
.changes-meta { font-family: var(--f-num); font-variant-numeric: tabular-nums; font-size: 11px; color: var(--ink-faint); margin-top: 4px; }

/* Manage-columns popup */
dialog.col-modal { width: min(384px, 92vw); overflow: hidden; }
.col-modal-head { background: var(--grad); color: #fff; padding: 16px 20px; }
.col-modal-head h2 { margin: 0; font: 700 16px var(--f-display); letter-spacing: -.01em; }
.col-modal-head p { margin: 4px 0 0; font-size: 12px; line-height: 1.4; opacity: .85; }
.col-list { padding: 8px; max-height: 56vh; overflow-y: auto; }
.col-opt { display: flex; align-items: center; gap: 11px; padding: 10px 12px; border-radius: 8px; cursor: pointer;
  font: 400 14px/1.4 var(--f-body); text-transform: none; letter-spacing: 0; color: var(--ink); margin: 0; }
.col-opt input { width: 16px; height: 16px; accent-color: var(--teal); cursor: pointer; }
.col-opt .name { font-size: 14px; }
.col-opt.locked { cursor: default; color: var(--ink-faint); }
@media (hover: hover) {
  .col-opt:hover { background: var(--surface-2); }
  .col-opt.locked:hover { background: none; }
}
.col-opt .lock { margin-left: auto; width: 14px; height: 14px; stroke-width: 2; color: var(--ink-faint); }
.col-modal-foot { display: flex; justify-content: flex-end; padding: 12px 16px; border-top: 1px solid var(--line); }

/* screen-reader-only text (e.g. a fieldset legend whose label already shows
   visually via an accordion header) */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---------- filters drawer (left flyout, Nordstrom-style; Workloads) ----------
   A <dialog> so Esc/backdrop-close and top-layer stacking come free; slides in
   from the left edge instead of the browser's default centered-fade. */
dialog.filter-drawer {
  position: fixed;
  /* --drawer-top = the app bar's current bottom edge, measured by filterbar.js at
     open time — the drawer (and backdrop) slide in BELOW the main nav, not over it */
  top: var(--drawer-top, 0px); left: 0; margin: 0;
  width: 380px; max-width: 92vw;
  height: calc(100vh - var(--drawer-top, 0px));
  max-height: calc(100vh - var(--drawer-top, 0px));
  border-radius: 0 var(--radius) var(--radius) 0;
  display: flex; flex-direction: column;
  padding: 0;
  transform: translateX(-100%);
  transition: transform .2s var(--ease-out-lg);
}
dialog.filter-drawer[open] { transform: translateX(0); }
/* Restored-after-auto-apply: open with no slide, so a filter tick doesn't look like
   the drawer left and came back (filterbar.js removes the class next frame). */
/* kills the slide-in AND the accordion height animation for the restore frame —
   a section still growing has nothing to scroll, so the restored scroll position
   would clamp to 0 (filterbar.js, drawer restore) */
dialog.filter-drawer.no-anim, dialog.filter-drawer.no-anim .acc-body { transition: none; }
dialog.filter-drawer::backdrop { background: rgba(11,40,42,.40); top: var(--drawer-top, 0px); }

/* Collapsed filters rail — the v2 pattern users know: a full-height strip pinned to
   the left edge that expands into the filters when clicked (v2: a fixed w-10 gray bar
   with a teal chevron). Here the expanded state IS .filter-drawer, which slides over
   the rail from the same edge. Page chrome, not a floating button: flat surface tone,
   hairline edge, no shadow. The chevron lines up with the filter bar's control row
   (--filterbar-mid / --nav-bottom, measured by filterbar.js); both have static
   fallbacks for before JS runs. */
.filter-rail {
  position: fixed; top: var(--nav-bottom, 58px); left: 0; bottom: 0; z-index: 4;
  width: 40px; padding: 0; margin: 0;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding-top: calc(var(--filterbar-mid, 134px) - var(--nav-bottom, 58px) - 9px);
  border: 0; border-right: 1px solid var(--line);
  background: var(--surface-2); color: var(--teal);
  cursor: pointer; text-align: center;
  transition: background .15s, border-color .15s;
}
.filter-rail .chev {
  width: 26px; height: 26px; stroke-width: 3.2; flex: none;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform .15s;
}
.filter-rail .rail-label {
  writing-mode: vertical-rl;
  font: 600 10.5px var(--f-display); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-faint);
}
@media (hover: hover) {
  .filter-rail:hover { background: var(--well); border-right-color: var(--line-strong); }
  .filter-rail:hover .chev { transform: translateX(2px); }
}
.filter-rail:focus-visible { outline: 2px solid var(--teal); outline-offset: -2px; }

/* Keep the rail out of the content's way: it occupies the page gutter rather than
   overlapping the cards (the drawer still slides over everything when open). */
.page:has(.filter-rail) { padding-left: 66px; }

.filter-drawer-head {
  display: flex; align-items: center; gap: 12px; padding: 16px 18px;
  border-bottom: 1px solid var(--line); flex: none;
}
.filter-drawer-head h2 { margin: 0; font: 700 18px var(--f-display); letter-spacing: -.01em; color: var(--ink); }
.drawer-close {
  width: 32px; height: 32px; border-radius: 8px; border: 0; background: none; padding: 0;
  color: var(--ink-soft); cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: background .15s, color .15s;
}
.drawer-close svg { width: 18px; height: 18px; stroke-width: 2; }
@media (hover: hover) {
  .drawer-close:hover { background: var(--surface-2); color: var(--ink); }
}

.filter-drawer-body { flex: 1 1 auto; overflow-y: auto; }

.acc-section { border-bottom: 1px solid var(--line); }
.acc-header {
  width: 100%; display: flex; align-items: center; gap: 10px; min-height: 40px; padding: 12px 18px;
  background: none; border: 0; cursor: pointer; text-align: left;
  font: 600 11.5px var(--f-display); text-transform: uppercase; letter-spacing: .07em; color: var(--ink);
  transition: background .15s;
}
@media (hover: hover) {
  .acc-header:hover { background: var(--surface-2); }
}
.acc-label { flex: 1; }
.acc-count {
  min-width: 19px; height: 19px; padding: 0 5px; border-radius: 999px;
  background: var(--teal-tint); color: var(--teal-deep);
  font: 700 11px var(--f-display); text-transform: none; letter-spacing: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.acc-count[hidden] { display: none; }
/* +/- indicator: a fixed horizontal bar plus a vertical bar that lies flat when open */
.acc-chev { width: 13px; height: 13px; position: relative; flex: none; }
.acc-chev::before, .acc-chev::after {
  content: ""; position: absolute; top: 50%; left: 50%; width: 9px; height: 1.6px;
  background: var(--ink-faint); border-radius: 1px; transform: translate(-50%, -50%);
}
.acc-chev::after { transform: translate(-50%, -50%) rotate(90deg); transition: transform .18s; }
.acc-section.open .acc-chev::after { transform: translate(-50%, -50%) rotate(0deg); }

/* grid-rows 0fr -> 1fr animates auto height, same technique as .filter-panel */
.acc-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .22s var(--ease-out-md); }
.acc-section.open .acc-body { grid-template-rows: 1fr; }
.acc-body > .acc-clip { overflow: hidden; }
.acc-inner { padding: 2px 18px 16px; display: flex; flex-direction: column; gap: 10px; }

.opt-search { position: relative; display: flex; align-items: center; }
.opt-search svg { position: absolute; left: 10px; width: 15px; height: 15px; color: var(--ink-faint); pointer-events: none; }
.opt-search input { height: 36px; padding-left: 32px; font-size: 13.5px; }

.opt-list { display: flex; flex-direction: column; max-height: 280px; overflow-y: auto; }
.opt-list label {
  display: flex; align-items: center; gap: 9px; padding: 8px 6px; border-radius: 6px; cursor: pointer;
  font: 400 14.5px/1.3 var(--f-body); text-transform: none; letter-spacing: 0; color: var(--ink); margin: 0;
  transition: background .12s;
}
@media (hover: hover) {
  .opt-list label:hover { background: var(--surface-2); }
}
.opt-list label[hidden] { display: none; }
.opt-list input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--teal); margin: 0; flex: none; }

.opt-nomatch { margin: 2px 6px; font-size: 12.5px; color: var(--ink-faint); }
.opt-nomatch[hidden] { display: none; }

/* Classification section reuses the drawer's vertical list rhythm instead of the
   flat panel's inline-wrap row (that layout is preserved as-is for any future
   non-drawer use of .class-opts; this override only applies inside the drawer). */
.filter-drawer .class-opts {
  flex-direction: column; flex-wrap: nowrap; align-items: stretch; height: auto; gap: 0;
  max-height: 280px; overflow-y: auto;
}
.filter-drawer .class-opts label { padding: 8px 6px; border-radius: 6px; white-space: normal; transition: background .12s; }
@media (hover: hover) {
  .filter-drawer .class-opts label:hover { background: var(--surface-2); }
}

.filter-drawer-foot {
  flex: none; display: flex; gap: 10px; padding: 14px 18px;
  border-top: 1px solid var(--line); background: var(--surface);
}
.filter-drawer-foot .btn { flex: 1; }

@media (max-width: 560px) {
  dialog.filter-drawer { width: 92vw; }
  /* On phones the rail would eat scarce width; the Filters button in the bar is
     right there, so the rail steps aside. */
  .filter-rail { display: none; }
  .page:has(.filter-rail) { padding-left: 26px; }
}

/* ---------- auth screens ---------- */

.auth-wrap {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--ground);
}
.auth-card {
  width: 100%;
  max-width: 400px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: var(--shadow-pop);
  padding: 34px 36px 30px;
  animation: slip-in .25s var(--ease-out-md);
  position: relative;
  overflow: hidden;
}
.auth-card::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: var(--grad); }
.auth-logo { display: block; height: 66px; width: auto; margin: 8px auto 22px; }
.auth-logo + .tagline { margin-top: -8px; }
.auth-card .tagline {
  text-align: center;
  font-family: var(--f-display);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 6px 0 26px;
}
.auth-card h1 { font-size: 17px; margin-bottom: 14px; }
.auth-card .flash { margin-bottom: 14px; }
.auth-card .aside { font-size: 12.5px; color: var(--ink-soft); margin-top: 14px; }
.auth-card .code-input {
  font-family: var(--f-num);
  font-variant-numeric: tabular-nums;
  font-size: 22px;
  letter-spacing: .35em;
  text-align: center;
}
.auth-card .qr { margin: 0 auto 14px; border: 1px solid var(--line); border-radius: 8px; padding: 6px; background: #fff; width: fit-content; }
/* base32 TOTP secret (letters + digits) — kept on --f-mono: disambiguating 0/O and 1/l/I
   when a user hand-types it into an authenticator app matters more here than de-terminaling it. */
.auth-card .secret {
  font-family: var(--f-mono);
  font-size: 13px;
  text-align: center;
  background: var(--surface-2);
  border: 1px dashed var(--line-strong);
  border-radius: 8px;
  padding: 6px 10px;
  margin-bottom: 14px;
  user-select: all;
}

/* ---------- detail lists (patient show) ---------- */

.dl { display: grid; grid-template-columns: 190px 1fr; gap: 0; font-size: 13.5px; }
.dl dt {
  font-family: var(--f-display);
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ink-faint);
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
}
.dl dd { margin: 0; padding: 7px 0; border-bottom: 1px solid var(--line); }
.dl dd:empty::after { content: "—"; color: var(--ink-faint); }
/* A .dl in a FULL-WIDTH card strands its values half a screen from their labels — the
   190px/1fr split was drawn for a half-width card. .dl.wide pairs them two-across
   instead (Casey, 08/06/26, when the patient page's Contact card moved up into the head
   and left Account alone on the row). Back to one pair per row when there isn't width
   for two — including inside the patient dialog, which is narrower than the page. */
.dl.wide { grid-template-columns: 190px minmax(0, 1fr) 190px minmax(0, 1fr); }
@media (max-width: 1100px) { .dl.wide { grid-template-columns: 190px minmax(0, 1fr); } }

/* ---------- patient strip (activity + show pages) ---------- */

/* Activity log header: identity left, who-you-call right (Casey, 08/05/26 — the single
   full-width slab was mostly empty space). The strip keeps its own margin-bottom for the
   pages that still use it alone, so the pair zeroes it and carries the gap itself. */
.patient-head {
  display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 14px; align-items: stretch; margin-bottom: 18px;
}
.patient-head > .card, .patient-head > .card + .card { margin: 0; }
/* 900, not 1040 (Casey, 08/17): 960 — half of a 1920 screen — is the width the heaviest
   user works at, and the pair has room there; the old breakpoint stacked it just above. */
@media (max-width: 900px) { .patient-head { grid-template-columns: 1fr; } }

/* Right-hand card: same tinted slab as the strip, rail on the right so the pair reads
   as two halves of one header rather than two unrelated cards. */
.account-side {
  position: relative; overflow: hidden;
  border: 1px solid var(--line); border-radius: 16px;
  background: linear-gradient(200deg, #f4faf9, var(--surface) 52%);
  box-shadow: var(--shadow-card);
}
.account-side::after {
  content: ""; position: absolute; right: 0; top: 0; bottom: 0; width: 4px;
  background: var(--grad-teal);
}
.account-side .card-body { padding: 14px 22px 14px 20px; }
.account-side .lbl {
  display: block; font-family: var(--f-display); font-weight: 600;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint);
}
.account-side .rp .who {
  display: block; font-family: var(--f-display); font-weight: 700; font-size: 16px;
  color: var(--ink); margin-top: 2px;
}
.account-side .rp .self { font-size: 12.5px; font-weight: 500; color: var(--teal-deep); }
.account-side .rp .unset { font-weight: 500; font-size: 14px; color: var(--ink-faint); }
.account-side .reach { margin: 10px 0 0; display: grid; gap: 4px; }
.account-side .reach .r { display: flex; gap: 10px; align-items: baseline; }
.account-side .reach dt {
  flex: 0 0 52px; font-family: var(--f-display); font-weight: 600;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-faint);
}
.account-side .reach dd { margin: 0; font-size: 13.5px; color: var(--ink); }
.account-side .reach dd a { color: var(--teal-deep); text-decoration: none; }
@media (hover: hover) {
  .account-side .reach dd a:hover { text-decoration: underline; }
}
/* The preferred method is marked, not moved — the rows stay in one order so the eye
   learns where each number lives. */
.account-side .reach .pref dt::after { content: " ★"; color: var(--teal); letter-spacing: 0; }
/* Account notes moved OUT of this card on 08/17 (it could grow without bound beside a
   fixed-height strip) and into their own card below — .notes-full carries them now. */
.notes-full { margin: 0; font-size: 14px; color: var(--ink); white-space: pre-wrap; }
.account-side .none { margin: 10px 0 0; font-size: 13px; color: var(--ink-faint); }
.account-side .none a { color: var(--teal-deep); }

/* Activity log: the right card holds the account notes (Casey, 09/21). The strip beside
   it sets the row height; a long note scrolls inside a capped box instead of stretching
   the pair (the 08/17 reason notes left the head). */
/* Flat surface, not the tinted slab: the scroll-shadow covers below are --surface, and
   on the tint they showed as a white patch at the top right. */
.notes-side { background: var(--surface); }
.notes-side .card-body { display: flex; flex-direction: column; height: 100%; }
.notes-side .rp { margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
.notes-side .notes-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.notes-side .notes-edit {
  font-family: var(--f-display); font-weight: 600; font-size: 12px;
  color: var(--teal-deep); text-decoration: none;
}
@media (hover: hover) { .notes-side .notes-edit:hover { text-decoration: underline; } }
.notes-side .notes-text {
  margin: 6px 0 0; font-size: 13.5px; line-height: 1.45; color: var(--ink);
  white-space: pre-wrap; overflow-wrap: anywhere;
  max-height: 7.4em; overflow-y: auto; padding-right: 6px;
  scrollbar-width: thin;
  /* Scroll shadows, no JS: the "local" covers ride with the text and hide the "scroll"
     shadows at either end, so a shadow shows only where there is more note to read. */
  background:
    linear-gradient(var(--surface) 30%, transparent) top / 100% 24px no-repeat local,
    linear-gradient(transparent, var(--surface) 70%) bottom / 100% 24px no-repeat local,
    radial-gradient(farthest-side at 50% 0, rgba(15, 60, 58, .16), transparent) top / 100% 10px no-repeat scroll,
    radial-gradient(farthest-side at 50% 100%, rgba(15, 60, 58, .16), transparent) bottom / 100% 10px no-repeat scroll;
}
.notes-side .notes-text:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; border-radius: 4px; }
.notes-side .none { margin-top: 6px; }

/* ---------- record edit head: editable card + read-only relationship card
   (Casey, 09/27/26 — Admin > Clients/Offices/Doctors had no way to see what's
   linked without clicking through). Same two-column contract as .patient-head:
   generous left column for the editable card, narrower read-only card on the
   right, stacking below the tablet breakpoint. Only an EXISTING record's form uses
   this — the New forms have nothing to show yet and keep the old single full-width
   card. */
.form-head {
  display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: 14px; align-items: start; margin-bottom: 14px;
}
.form-head > .card { margin: 0; }
@media (max-width: 900px) { .form-head { grid-template-columns: 1fr; } }
/* The editable card keeps ONE copy of its fields for both layouts. Full width (a New
   record) its .grid rows lay out as written; inside .form-head the rows dissolve
   (display: contents) and every field flows two-up, so rows written three-wide can't
   leave a ragged third. Notes and help text span both columns. */
.form-head .form-flow { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px 18px; align-content: start; }
.form-head .form-flow > .grid { display: contents; }
.form-head .form-flow > :not(.grid) { grid-column: 1 / -1; margin: 0; }
@media (max-width: 600px) { .form-head .form-flow { grid-template-columns: 1fr; } }

/* The relationship card itself: read-only (links only, no inputs), height-capped so
   a client with dozens of offices, or an office with dozens of doctors, can't push
   the rest of the form down — same capped-scroll idea as .notes-side .notes-text,
   simpler because a tree of links has no need for the scroll-shadow gradient. */
.rel-card .card-body { max-height: 420px; overflow-y: auto; }
.rel-tree { list-style: none; margin: 0; padding: 0; }
.rel-tree > li { margin-bottom: 12px; }
.rel-tree > li:last-child { margin-bottom: 0; }
.rel-office { font-family: var(--f-display); font-weight: 700; font-size: 13.5px; color: var(--ink); }
.rel-office a { color: inherit; text-decoration: none; }
@media (hover: hover) { .rel-office a:hover { color: var(--teal-deep); text-decoration: underline; } }
.rel-doctors { list-style: none; margin: 6px 0 0; padding: 0 0 0 14px; border-left: 2px solid var(--line); }
.rel-doctors li { font-size: 13px; padding: 3px 0 3px 10px; }
.rel-doctors a { color: var(--teal-deep); text-decoration: none; }
@media (hover: hover) { .rel-doctors a:hover { text-decoration: underline; } }
/* Flat variant: the office edit form's Doctors card is a single list, not a tree
   nested under an office, so it drops the tree's indent/rail. */
.rel-doctors.flat { margin: 0; padding: 0; border-left: none; }

/* ---------- initials colors (migrations 022/023; Casey, 09/21) ----------
   Each user's color arrives as a .u-<id> class from /avatar-colors.css (generated; the
   CSP forbids inline styles), which sets --av (background) and --av-ink (letters: white,
   or dark on a pale pick). Anything painting a circle reads those two. */
.avatar-preview { background: var(--av, var(--blue-deep)); color: var(--av-ink, #fff); transition: background .15s, color .15s; }

/* Activity log "By": the author's initials circle (never the photo). Monotone, the
   same light gray + gray ink as the System gear — Casey, 09/29/26: the per-user colors
   were distracting, so the column also moved to the right of Details. The name is a
   tooltip on hover AND on focus, so a tap shows it on a phone. It opens to the LEFT,
   over the Details column, because the table's scroll wrapper would clip it above the
   first row or past the actions column. */
.ledger td.by { width: 1%; white-space: nowrap; }
.who-dot {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--surface-2); color: var(--ink-faint); box-shadow: inset 0 0 0 1px var(--line);
  font: 700 10.5px var(--f-display); letter-spacing: .02em;
  cursor: default; outline: none;
}
.who-dot.sys svg { width: 15px; height: 15px; }
.who-dot:focus-visible { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--teal); }
.who-dot::after {
  content: attr(data-tip);
  position: absolute; right: calc(100% + 8px); top: 50%; z-index: 5;
  transform: translate(4px, -50%); opacity: 0; pointer-events: none;
  padding: 5px 9px; border-radius: 7px; white-space: nowrap;
  background: var(--ink); color: #fff;
  font: 600 12px var(--f-body, inherit); letter-spacing: 0;
  box-shadow: 0 4px 14px rgba(11,40,42,.18);
  transition: opacity .12s ease, transform .12s ease;
}
.who-dot:focus::after { opacity: 1; transform: translate(0, -50%); }
@media (hover: hover) {
  .who-dot:hover::after { opacity: 1; transform: translate(0, -50%); }
}
@media (prefers-reduced-motion: reduce) {
  .who-dot::after, .avatar-preview { transition: none; }
}

/* Small "i" help badge beside a field label; the explanation is a tooltip on hover AND
   focus (a tap shows it on a phone). Opens above and to the right (labels sit at a card's
   left edge, so centering would push it off-screen on a phone), wrapping to a readable width. First
   use: the doctor form's Nickname (Casey, 09/30/26 — "just for how it shows in filters"). */
.info-tip {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; margin-left: 4px; vertical-align: -3px; border-radius: 50%;
  background: var(--surface-2); color: var(--ink-faint); box-shadow: inset 0 0 0 1px var(--line-strong);
  font: 700 10px/1 var(--f-body); font-style: italic; text-transform: none; letter-spacing: 0;
  cursor: help; outline: none;
}
.info-tip:focus-visible { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--teal); }
.info-tip::after {
  content: attr(data-tip);
  position: absolute; left: -8px; bottom: calc(100% + 8px); z-index: 20;
  width: max-content; max-width: 260px;
  transform: translateY(4px); opacity: 0; pointer-events: none;
  padding: 7px 10px; border-radius: 7px;
  background: var(--ink); color: #fff;
  font: 500 12px/1.4 var(--f-body); font-style: normal; text-align: left;
  box-shadow: 0 4px 14px rgba(11,40,42,.18);
  transition: opacity .12s ease, transform .12s ease;
}
.info-tip:focus::after { opacity: 1; transform: none; }
@media (hover: hover) {
  .info-tip:hover::after { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .info-tip::after { transition: none; } }

/* The color picker: the native color input, sized as a round swatch. */
.color-pick-row { display: flex; align-items: center; gap: 12px; }
.color-pick-row .help { margin-top: 0; }
.color-pick input[type="color"] {
  flex: none; width: 40px; height: 40px; padding: 0; border: 0; border-radius: 50%;
  background: none; cursor: pointer;
  box-shadow: 0 0 0 1px var(--line-strong);
}
.color-pick input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.color-pick input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 50%; }
.color-pick input[type="color"]::-moz-color-swatch { border: 0; border-radius: 50%; }
.color-pick input[type="color"]:disabled { opacity: .45; cursor: default; }
.color-pick-row .check { display: inline-flex; align-items: center; gap: 6px; font-weight: 500; }
.color-pick input[type="color"]:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--teal); }
.color-form { margin-top: 18px; }
.profile-color { flex-basis: 100%; }

/* the page's anchor: a lightly tinted, elevated slab with a teal accent rail */
.patient-strip {
  position: relative; overflow: hidden; margin-bottom: 18px;
  border: 1px solid var(--line); border-radius: 16px;
  background:
    radial-gradient(130% 150% at 96% -20%, rgba(55,168,159,.10), transparent 52%),
    linear-gradient(160deg, #f4faf9, var(--surface) 46%);
  box-shadow: var(--shadow-card);
}
.patient-strip::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: var(--grad-teal);
}
.patient-strip .card-body { display: flex; flex-wrap: wrap; gap: 6px 26px; align-items: baseline; padding: 14px 20px 14px 22px; }
.patient-strip .name {
  font-family: var(--f-display); font-weight: 700; font-size: 20px;
  background: var(--grad-ink);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.patient-strip .name a {
  background: var(--grad-ink);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
/* Identity stacks: name, then AT ID, then default date (Casey, 08/05/26). It is a flex
   ITEM of the baseline-aligned card body, so its first line — the name — is what the
   status pills and Next call line up against, exactly as when they were siblings. */
.patient-strip .ident { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.patient-strip .ident .ddate { margin-top: 2px; }
.patient-strip .pill.off { background: #fff; color: var(--ink-soft); border-color: var(--line-strong); }
.patient-strip .atid { font-family: var(--f-num); font-variant-numeric: tabular-nums; font-size: 12.5px; color: var(--ink-soft); }
.patient-strip .meta {
  flex-basis: 100%; margin: 4px 0 0;
  /* 110/18, not 150/26: at 960 the strip is half the head (~508px of usable body), and the
     old minimum fit only three of the four, orphaning Rep onto its own row. */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 10px 18px; padding-top: 12px; border-top: 1px solid var(--line);
}
.patient-strip .meta .m { display: flex; flex-direction: column; gap: 2px; }
.patient-strip .meta dt {
  font: 600 10.5px var(--f-display); text-transform: uppercase; letter-spacing: .06em;
  color: var(--teal); }
.patient-strip .meta dd { margin: 0; font-size: 13.5px; color: var(--ink); }
.patient-strip .addr-warn { flex-basis: 100%; margin-top: 10px; }
.patient-strip .ddate { display: inline-flex; align-items: baseline; gap: 6px; }
.patient-strip .ddate .k {
  font: 600 10.5px var(--f-display); text-transform: uppercase; letter-spacing: .06em;
  color: var(--teal); }
.patient-strip .ddate .v { font-family: var(--f-num); font-variant-numeric: tabular-nums; font-size: 13px; color: var(--ink); }
.patient-strip .due { margin-left: auto; text-align: right; }
.patient-strip .due .d { font-family: var(--f-num); font-variant-numeric: tabular-nums; font-size: 16px; font-weight: 500; color: var(--teal-deep); }
.patient-strip .due .lbl-inline { font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint); font-family: var(--f-display); font-weight: 600; }

/* ---------- back to top (every signed-in page) ----------
   Casey, 08/05/26: "a back to top button when the page has to scroll down for a long
   activity log or report". global.js adds .on past a screen-and-a-bit of scroll; the
   button stays in the DOM so it can fade rather than pop, and visibility:hidden keeps
   it out of the tab order and the a11y tree while it is invisible. */
.to-top {
  position: fixed; right: 22px; bottom: 22px; z-index: 5;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 14px 9px 11px; border: 0; border-radius: 999px;
  background: var(--grad-teal); color: #fff;
  font: 600 12px var(--f-display); letter-spacing: .04em; text-transform: uppercase;
  box-shadow: var(--shadow-card-hi); cursor: pointer;
  opacity: 0; visibility: hidden; transform: translateY(8px);
  transition: opacity .16s var(--ease-out-sm), transform .16s var(--ease-out-sm), box-shadow .16s var(--ease-out-sm);
}
.to-top.on { opacity: 1; visibility: visible; transform: none; }
@media (hover: hover) {
  .to-top:hover { box-shadow: 0 4px 10px rgba(11,40,42,.12), 0 20px 44px rgba(11,40,42,.20); }
  .to-top.on:hover { transform: translateY(-2px); }
}
.to-top.on:active { transform: translateY(0); }
.to-top:focus-visible { outline: 2px solid var(--teal-deep); outline-offset: 3px; }
.to-top svg { width: 16px; height: 16px; }
/* On a phone the label costs more than it explains — arrow only, and clear of the
   thumb rail at the bottom edge. */
@media (max-width: 600px) {
  .to-top { right: 14px; bottom: 16px; padding: 11px; }
  .to-top span { display: none; }
}
@media (prefers-reduced-motion: reduce) { .to-top { transition: none; } }

/* ---------- misc ---------- */

.lede { font-size: 12.5px; color: var(--ink-soft); margin: 0 0 14px; }
.center { text-align: center; }
.keylabel { text-align: center; margin: 0 0 4px; font-size: 12.5px; color: var(--ink-soft); }
.hidden { display: none !important; }
.inline-form { display: inline-block; margin: 0; vertical-align: middle; }
.muted { color: var(--ink-faint); }
.small { font-size: 13px; }
.right { text-align: right; }
.nowrap { white-space: nowrap; }
.mt0 { margin-top: 0; }
.spread { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
.stack > * + * { margin-top: 10px; }

/* ---------- rep phone scopes (docs/SPEC-REP-PHONE-SCOPES.md, admin user form) ----------
   "Phone on letters" repeating rows: each is its own block (not one run-on grid) so
   several exceptions read as a list, separated by a hairline. The row's own .grid.c4
   already stacks to one column at <=600px (existing breakpoint), so the card needs no
   phone-specific responsive rule of its own. */
.rep-phone-row { padding: 14px 0; border-bottom: 1px solid var(--line); }
.rep-phone-row:first-child { padding-top: 0; }
.rep-phone-row:last-child { border-bottom: none; padding-bottom: 0; }
.rep-phone-row-actions { display: flex; align-items: flex-end; }
/* the small button centred on the 39px input beside it, not sitting on its baseline */
.rep-phone-row-actions .btn { margin-bottom: 7px; }
/* Amber "worth a look, not wrong" — the doctor/office overlap warning (spec: warn, don't
   block). Same ink as .pill.note (app.css tokens), just for a full sentence instead of a
   chip. */
.help.note { color: var(--warn-ink); }

/* ---------- layout utilities (Casey, 08/06/26 — approved instead of adopting Tailwind) ----
   Deliberately SMALL, and an extension of the six above, not a second system. They exist
   because CSP forbids `style=""`, so without them every one-off nudge has to become a
   bespoke named class in this file.
   THE RULES, so this can't drift into a homegrown half-Tailwind:
   1. Layout and spacing ONLY. Colour, border, radius, shadow and type stay in components
      and tokens — that is what keeps a per-client re-theme to overriding a few custom
      properties instead of editing templates.
   2. A utility is one class (0,1,0), so a component rule like `.card .field {…}` BEATS it
      and the utility silently does nothing. The answer is a component rule, never
      `!important` — an important-laden layer fights the design system instead of serving it.
   3. Adding to this list needs a reason. Reach for the existing members first:
      .muted .small .right .center .nowrap .tnum .hidden .mt0 .spread .stack .grid.c2/c3/c4
   Spacing steps are the rhythm already in this file (4 / 8 / 14 / 18 / 24), and the
   no-hyphen names follow .mt0's precedent. */
.row { display: flex; align-items: center; gap: 8px; }
.row.wrap { flex-wrap: wrap; }
.items-start { align-items: flex-start; }
.items-end { align-items: flex-end; }
.grow { flex: 1 1 auto; min-width: 0; } /* min-width:0 so a long name ellipsizes instead of pushing siblings out */
.w-full { width: 100%; }
.gap1 { gap: 4px; }
.gap2 { gap: 8px; }
.gap3 { gap: 14px; }
.gap4 { gap: 18px; }
.gap5 { gap: 24px; }
.mt1 { margin-top: 4px; }
.mt2 { margin-top: 8px; }
.mt3 { margin-top: 14px; }
.mt4 { margin-top: 18px; }
.mt5 { margin-top: 24px; }
.mb0 { margin-bottom: 0; }
.mb1 { margin-bottom: 4px; }
.mb2 { margin-bottom: 8px; }
.mb3 { margin-bottom: 14px; }
.mb4 { margin-bottom: 18px; }
.mb5 { margin-bottom: 24px; }
/* Phone escape hatch for secondary content — same breakpoint the cards use. */
@media (max-width: 600px) { .hide-sm { display: none !important; } }

.pager { display: flex; gap: 8px; align-items: center; justify-content: flex-end; padding: 12px 0; font-size: 13px; }
/* pager.js jumps here after a page change instead of leaving you at the top of the
   document; the offset keeps the card head clear of the viewport edge. */
[data-results] { scroll-margin-top: 14px; }
.pager .pg { font-family: var(--f-num); font-variant-numeric: tabular-nums; font-size: 12px; color: var(--ink-faint); }

footer.colophon {
  text-align: center;
  color: var(--ink-faint);
  font-size: 11.5px;
  letter-spacing: .02em;
  max-width: 1260px;
  margin: 0 auto;
  padding: 0 26px 28px;
}

/* Labels stay on all the way down to the phone hamburger (600px) — Evelyn runs the app
   in a narrow window and icon-only tabs were unreadable there (Casey, 10/02/26). The
   room comes from tighter cells and a smaller label instead, and from 960px (not 820)
   so the search box isn't crushed to a sliver between 820 and 960. */
@media (max-width: 960px) {
  .mainnav { gap: 0; margin-left: 4px; }
  .mainnav a, .mainnav button { min-width: 0; padding: 9px 9px 7px; font-size: 11px; }
  .user-menu-trigger .user-menu-name { display: none; }
  .user-menu-trigger { padding: 5px; }
}
/* last stretch before the hamburger: smaller logo, tighter cells, and a floor under the
   search so it stays usable (the 560px phone rules below take over from there) */
@media (max-width: 720px) {
  .appbar { padding-right: 10px; }
  .brand { padding: 0 10px; margin-right: 4px; }
  .brand-logo { height: 36px; }
  .mainnav a, .mainnav button { padding: 9px 6px 7px; }
  .appbar-right { gap: 8px; }
  .nav-search { min-width: 72px; }
}

/* phones: everything must fit without horizontal scroll — search shrinks, fixed
   widths give way to the space left over */
@media (max-width: 560px) {
  .appbar { padding-right: 10px; }
  .brand { padding: 0 10px; margin-right: 4px; }
  .brand-logo { height: 38px; }
  .mainnav { gap: 0; margin-left: 4px; }
  .mainnav a, .mainnav button { min-width: 40px; padding: 9px 8px; }
  .appbar-right { gap: 8px; flex: 1 1 auto; min-width: 0; justify-content: flex-end; }
  .nav-search { flex: 1 1 auto; min-width: 0; max-width: 200px; }
  .nav-search input[type=search], .nav-search input:focus { width: 100%; }
  .nav-search-menu { width: 300px; max-width: calc(100vw - 20px); }
}

/* Very narrow phones (375/390/430 device widths — Casey tests from his phone). The
   560px pass above never truly overflows here (.nav-search's flex:1 1 auto/min-width:0
   just absorbs the deficit) — but it also let .brand's flex item shrink below its SVG
   logo's intrinsic aspect ratio (the base .brand flex-shrink:0 rule above stops that),
   so the room has to come from somewhere else: the brand rail, the nav-icon cells, the
   two remaining gaps, and a smaller (but not distorted) logo. Search gets an explicit
   floor instead of shrinking to nothing — measured 56–96px depending on exact width,
   legible as "Find pat…" rather than a single letter.
   Retuned 08/13/26 for the dashboard nav item (docs/SPEC-DASHBOARD.md): a 5th icon
   pushed .mainnav's own min-content past what was left at 375px, and since
   .appbar-right carries min-width:0 (560px rule, needed so IT can shrink), the overflow
   didn't wrap or clip — it bled left, .nav-search rendering on top of the last two nav
   icons (justify-content:flex-end packs an overflowing flex box from its end backward).
   The numbers below were re-picked so brand+mainnav(5 items)+appbar-right's own floor
   (search 56 + gap + avatar) sum under 375px again, confirmed by measuring every
   element's getBoundingClientRect for overlap, not just eyeballing a screenshot. */
@media (max-width: 430px) {
  .brand { padding: 0 5px; }
  .brand-logo { height: 27px; }
  .mainnav { margin-left: 0; gap: 1px; }
  .mainnav a, .mainnav button { min-width: 29px; padding: 9px 2px; }
  .appbar-right { gap: 4px; }
  .user-menu-trigger { padding: 4px; gap: 6px; }
  .nav-search { min-width: 52px; }
}

@media (prefers-reduced-motion: reduce) {
  .filter-panel, .filters-toggle .chev, .showdd-btn .chev, .showdd-menu, .user-menu-trigger .caret,
  dialog.filter-drawer, .acc-body, .acc-chev::after, .filter-rail, .filter-rail .chev { transition: none; }
  .flash, .auth-card { animation: none; }
}

/* ---------- backup codes (migration 032) ---------- */

/* Admin user form: the "N of 10 unused · generated …" status line. */
.backup-status { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; margin-bottom: 14px; }
.backup-meta { font-size: 13px; color: var(--ink-soft); }

/* "Use a backup code instead" disclosure on /2fa and /2fa/email: a quiet text link that
   opens a small form; closed by default so the normal code stays the obvious path. */
.backup-alt { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.backup-alt summary {
  cursor: pointer; list-style: none; font-size: 12.5px; color: var(--teal);
  width: fit-content; margin: 0 auto;
}
.backup-alt summary::-webkit-details-marker { display: none; }
.backup-alt summary:hover { text-decoration: underline; }
.backup-alt summary:focus-visible { outline: 2px solid var(--teal-bright); outline-offset: 3px; border-radius: 3px; }
.backup-alt[open] summary { margin-bottom: 12px; }
.backup-alt .lede { font-size: 13px; color: var(--ink-soft); margin-bottom: 12px; }
.auth-card .backup-input {
  font-family: var(--f-mono); font-size: 18px; letter-spacing: .12em; text-align: center;
}
.auth-card .btn.secondary.wide { margin-top: 2px; }

/* The one-time codes page: ten codes, two columns, in the mono face so 2/z and 5/s
   can't be misread off a printout. Print keeps just this sheet. */
.backup-warn {
  margin: 0 0 14px; padding: 9px 12px; font-size: 13px; color: var(--warn-ink);
  background: var(--warn-tint); border: 1px solid #edd9ae; border-radius: 8px;
}
.backup-codes {
  list-style: none; margin: 0 0 14px; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 14px;
  counter-reset: bc;
}
.backup-codes li {
  counter-increment: bc; display: flex; align-items: baseline; gap: 10px;
  padding: 10px 14px; background: var(--surface-2); border: 1px dashed var(--line-strong); border-radius: 8px;
}
.backup-codes li::before {
  content: counter(bc) "."; min-width: 1.6em; font-size: 12px; color: var(--ink-faint);
  font-family: var(--f-num); font-variant-numeric: tabular-nums;
}
.backup-codes code { font-family: var(--f-mono); font-size: 17px; letter-spacing: .08em; color: var(--ink); user-select: all; }
@media (max-width: 480px) {
  .backup-codes { grid-template-columns: 1fr; }
}

@media print {
  .no-print { display: none; }
  .backup-warn { border: 1px solid #000; background: none; color: #000; }
  .backup-codes li { border: 1px solid #000; background: none; break-inside: avoid; }
  .backup-codes code { color: #000; font-size: 16pt; }
  .appbar, .subnav, .filterbar, .form-actions, footer.colophon, .page-head .actions, .filter-rail { display: none; }
  .page:has(.filter-rail) { padding-left: 26px; }
  html, body { background: #fff; }
  .card { box-shadow: none; }
}

/* ---------- entrance: a single subtle rise as page cards paint ---------- */

@media (prefers-reduced-motion: no-preference) {
  .page > .card { animation: card-rise .3s var(--ease-out-md) both; }
  .page > .card:nth-child(2) { animation-delay: .06s; }
  .page > .card:nth-child(3) { animation-delay: .12s; }
  .page > .card:nth-child(4) { animation-delay: .18s; }
  /* live dot on the patient strip's Active pill */
  .patient-strip .pill.on::before {
    content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 6px;
    border-radius: 50%; background: #fff; vertical-align: middle;
    animation: dot-pulse 2.4s ease-in-out infinite;
  }
  .live-dot { animation: dot-pulse 2.4s ease-in-out infinite; }
  /* Admin → Active sessions reloads itself every 30s; replaying the rise each time would
     make the page twitch. */
  .page > .card.no-rise { animation: none; }
}
@keyframes card-rise { from { opacity: 0; transform: translateY(14px); } }
@keyframes dot-pulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .55; transform: scale(1.35); } }

/* Admin → Sign-ins: the summary tiles sit above a filter bar, not a card. */
.signins-summary { margin-bottom: 18px; }

/* Admin → Active sessions: "live" marker beside the refresh note. */
.live-hint { display: inline-flex; align-items: center; gap: 7px; }
.live-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--teal); flex: none; }
/* Paused after 10 idle minutes so an open tab can't keep the session alive. */
.live-resume {
  display: inline-flex; align-items: center; gap: 7px; padding: 0; border: 0;
  background: none; font: inherit; color: var(--ink-soft); cursor: pointer;
}
.live-resume:hover { color: var(--teal-deep); text-decoration: underline; }
.live-hint.paused .live-dot { background: var(--ink-faint); animation: none; }

/* Audit Log rows (Casey 09/22: readable details). Action in words with what it was
   done to underneath; details as a label/value list with old → new for changes. */
.ledger.audit td { vertical-align: top; }
.audit-what { font-weight: 600; color: var(--ink); white-space: nowrap; }
.audit-about { margin-top: 2px; font-size: 12.5px; color: var(--ink-soft); }
.audit-about .mono { font-size: 11.5px; color: var(--ink-faint); }
.audit-lines {
  display: grid; grid-template-columns: max-content 1fr; gap: 3px 14px;
  margin: 0; font-size: 13px;
}
.audit-lines dt { color: var(--ink-faint); }
.audit-lines dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; }
.audit-from { color: var(--ink-faint); text-decoration: line-through; text-decoration-color: var(--line-strong); }
.audit-arrow { margin: 0 6px; color: var(--ink-faint); }
.audit-to { font-weight: 600; }

/* Show/hide eye on new-password fields (global.js wraps each one). */
.pw-wrap { position: relative; display: block; }
.pw-wrap input { padding-right: 42px; }
.pw-eye {
  position: absolute; top: 50%; right: 5px; transform: translateY(-50%);
  width: 32px; height: 32px; display: grid; place-items: center;
  border: 0; border-radius: 8px; background: transparent; color: var(--ink-faint);
  cursor: pointer; transition: color .15s, background .15s;
}
.pw-eye:hover { color: var(--teal); background: rgba(0,130,126,.08); }
.pw-eye svg { width: 18px; height: 18px; }

/* Admin → Users → Access controls: the Set password block under the reset buttons. */
.set-password { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.set-password h3 { margin: 0 0 10px; font-size: 14px; display: flex; align-items: center; gap: 8px; }
.set-password .grid { margin-bottom: 12px; }

/* Confirm-password modal (layout.html, global.js). */
.sudo-modal { width: min(420px, 92vw); }
.sudo-modal .sudo-error { margin: 0 0 12px; }

/* ======================================================================================
   PHONES (<= 600px) — the mobile pass (Casey, 10/01/26: "optimize the navigation for
   mobile as well as the admin pages ... I don't wanna mess up any of the navigations for
   anything besides mobile"). Target: iPhone 17 Pro Max, 440 CSS px.

   THE RULE FOR THIS BLOCK: nothing above 600px may change. Every declaration below sits
   inside the one query, except `.nav-toggle { display: none }` which hides markup that only
   exists for phones. It is the LAST block in the file on purpose — it overrides the older
   820/560/430 app-bar rules above (icon-only nav, squeezed search) by order, at equal
   specificity. 600 is the breakpoint the rest of the file already uses for phones
   (.grid.c2-c4, .hide-sm, .to-top, .form-head). Admin-only rules hang off `.page-admin`
   (layout.html adds it to <main> on /admin/*), so the patient/workload/letter/report
   screens are untouched beyond the shared app bar.
   ====================================================================================== */

.nav-toggle { display: none; }
.mainnav a.nav-admin-sm { display: none; }  /* phones only — see layout.html */

@media (max-width: 600px) {

  /* ---------- app bar: logo · search · avatar · menu button; the nav lives in a panel ---------- */

  /* sticky: on a 14,000px user form the nav was a long scroll away. scroll-padding keeps
     pager/anchor jumps from landing under the bar. */
  html { scroll-padding-top: 64px; }
  .appbar {
    position: sticky; top: 0; align-items: center; gap: 8px;
    min-height: 56px; padding: 0 8px 0 0;
  }
  .brand { align-self: stretch; padding: 0 12px; margin-right: 0; }
  .brand-logo { height: 36px; }

  .appbar-right { flex: 1 1 auto; min-width: 0; gap: 8px; padding: 6px 0; justify-content: flex-end; }
  /* 360-390px phones: a slimmer logo block hands the room to the search field */
  @media (max-width: 400px) {
    .brand { padding: 0 8px; }
    .brand-logo { height: 30px; }
  }

  /* search: as wide as the bar allows; 16px so iOS Safari doesn't zoom on focus */
  .nav-search { position: static; flex: 1 1 auto; min-width: 0; max-width: none; }
  .nav-search input[type=search], .nav-search input:focus {
    width: 100%; height: 44px; padding: 0 12px; font-size: 16px; border-radius: 10px;
  }
  /* results: a sheet under the bar, edge to edge, not a 300px box hanging off a narrow field */
  .nav-search-menu {
    left: 8px; right: 8px; top: calc(100% + 6px); width: auto; max-width: none;
    max-height: min(60dvh, 420px); -webkit-overflow-scrolling: touch;
  }
  .nav-search-row { min-height: 56px; justify-content: center; padding: 8px 12px; }
  .nav-search-row .n { font-size: 15px; }
  .nav-search-row .m { font-size: 12.5px; }
  .nav-search-none { padding: 14px 12px; font-size: 14px; }

  /* user menu: a 44px avatar button; its panel hangs off the bar's right edge */
  .appbar-right, .user-menu { position: static; }
  .user-menu-trigger { width: 44px; height: 44px; padding: 0; justify-content: center; border-radius: 50%; }
  .user-menu-trigger .caret { display: none; }
  .user-menu-trigger .avatar { width: 34px; height: 34px; font-size: 12.5px; }
  .user-menu-panel {
    top: calc(100% + 6px); right: 8px; left: auto; min-width: 0; max-width: none;
    width: min(290px, calc(100vw - 16px)); padding: 8px;
  }
  .user-menu-head { padding: 10px 12px 12px; }
  .user-menu-item { min-height: 48px; padding: 0 12px; gap: 12px; font-size: 15px; border-radius: 8px; }
  .user-menu-item svg { width: 20px; height: 20px; }

  /* the menu button (the markup is display:none above this breakpoint) */
  .nav-toggle {
    display: inline-flex; align-items: center; justify-content: center; flex: none;
    width: 44px; height: 44px; padding: 0; margin: 0; color: #fff; cursor: pointer;
    background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.22); border-radius: 12px;
    transition: background .15s var(--ease-out-sm); -webkit-tap-highlight-color: transparent;
  }
  .nav-toggle svg { width: 24px; height: 24px; }
  .nav-toggle path {
    transform-box: fill-box; transform-origin: center;
    transition: transform .2s var(--ease-out-md), opacity .15s var(--ease-out-sm);
  }
  .appbar.nav-open .nav-toggle { background: rgba(255,255,255,.26); }
  .appbar.nav-open .nt-top { transform: translateY(5px) rotate(45deg); }
  .appbar.nav-open .nt-mid { opacity: 0; }
  .appbar.nav-open .nt-bot { transform: translateY(-5px) rotate(-45deg); }

  /* the nav: a full-width panel under the bar (icon + label rows, 52px tall). Hidden with
     visibility so its links leave the tab order; opacity + transform only for the reveal. */
  .mainnav {
    position: absolute; top: 100%; left: 0; right: 0; z-index: 40; margin: 0; gap: 2px;
    flex-direction: column; align-items: stretch; padding: 8px 8px calc(10px + env(safe-area-inset-bottom));
    background: var(--surface); border-bottom: 1px solid var(--line); box-shadow: var(--shadow-pop);
    opacity: 0; visibility: hidden; pointer-events: none; transform: translateY(-8px);
    transition: opacity .18s var(--ease-out-md), transform .18s var(--ease-out-md), visibility 0s linear .18s;
  }
  .appbar.nav-open .mainnav {
    opacity: 1; visibility: visible; pointer-events: auto; transform: none; transition-delay: 0s;
  }
  .mainnav a, .mainnav button {
    flex-direction: row; justify-content: flex-start; gap: 16px; width: 100%; min-width: 0; min-height: 52px;
    padding: 0 14px; border: 0; border-radius: 10px; text-align: left;
    font-size: 16px; font-weight: 600; color: var(--ink);
  }
  .mainnav a span, .mainnav button span { display: inline; }
  .mainnav a.nav-admin-sm { display: flex; }
  .mainnav a svg, .mainnav button svg { width: 22px; height: 22px; flex: none; color: var(--teal); }
  .mainnav a.active { color: var(--teal-deep); background: var(--teal-tint); box-shadow: inset 3px 0 0 var(--teal); }
  @media (hover: hover) {
    .mainnav a:hover, .mainnav button:hover { color: var(--teal-deep); background: var(--teal-tint); }
  }
  /* dim the page behind the open panel; a tap on it closes (mobile.js) */
  body:has(.appbar.nav-open)::after {
    content: ""; position: fixed; inset: 0; z-index: 4; background: rgba(11,40,42,.42);
    animation: nav-scrim .2s var(--ease-out-sm);
  }
  @keyframes nav-scrim { from { opacity: 0; } }

  /* admin sub-nav: one scrolling row of 48px targets, faded at the right edge as a "more"
     hint (the trailing spacer keeps the last link clear of the fade) */
  .subnav {
    padding: 0 8px; gap: 0; scrollbar-width: none; -webkit-overflow-scrolling: touch;
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
            mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
  }
  .subnav::-webkit-scrollbar { display: none; }
  .subnav::after { content: ""; flex: none; width: 28px; }
  .subnav a { display: flex; align-items: center; min-height: 48px; padding: 0 14px; font-size: 14px; }

  /* ---------- modals that belong to the chrome: feedback, confirm-password, unsaved ---------- */

  #whatsNewModal, #feedbackModal, #sudo-modal, #unsaved-block-modal, .page-admin dialog {
    width: calc(100vw - 24px); max-width: 480px; max-height: calc(100dvh - 24px);
  }
  #whatsNewModal > .card-body, #feedbackModal > .card-body, #sudo-modal > .card-body, #unsaved-block-modal > .card-body,
  .page-admin dialog > .card-body { max-height: calc(100dvh - 110px); }
  #feedbackModal .btn, #sudo-modal .btn, #unsaved-block-modal .btn, .page-admin dialog .btn { min-height: 44px; flex: 1 1 auto; }
  #feedbackModal .form-actions, #sudo-modal .form-actions, #unsaved-block-modal .form-actions { flex-wrap: wrap; }
  #feedbackModal :is(input:not([type=file]), textarea), #sudo-modal input { font-size: 16px; min-height: 44px; }

  /* ---------- admin pages ---------- */

  .page-admin { padding: 18px 12px 48px; }
  .page-admin .page-head { flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-bottom: 14px; }
  .page-admin .page-head h1 { font-size: 24px; }
  .page-admin .page-head .rule { display: none; }
  .page-admin .page-head .actions { margin-left: auto; }
  .page-admin .card-head { flex-wrap: wrap; padding: 12px 14px; gap: 2px 10px; }
  .page-admin .card-head .hint { flex: 1 1 100%; }
  .page-admin .card-body:not(.tight) { padding: 14px; }

  /* buttons and controls: 44px targets, 16px type so iOS never zooms a focused field */
  .page-admin .btn { min-height: 44px; padding: 0 16px; font-size: 13.5px; }
  .page-admin .btn.small { padding: 0 14px; font-size: 13px; }
  .page-admin :is(input:not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=file]):not([type=color]):not([type=range]), select, textarea) {
    font-size: 16px; min-height: 44px;
  }
  /* WebKit's menulist ignores min-height; an explicit height is what it honours */
  .page-admin select { height: 44px; }
  .page-admin textarea { min-height: 96px; }
  .page-admin input[type=file] { min-height: 44px; padding: 11px 0; font-size: 16px; max-width: 100%; }
  .page-admin .color-pick input[type="color"] { width: 44px; height: 44px; }
  .page-admin .pw-wrap input { padding-right: 52px; }
  .page-admin .pw-eye { width: 44px; height: 44px; right: 0; }
  /* small in-text links keep their look but get a 44px hit area (padding grown, margin
     pulled back, so no layout moves) */
  .page-admin .rel-office a, .page-admin .rel-doctors a, .page-admin .audit-about a, .page-admin p.small > a,
  .page-admin table.stack-sm tbody td:not(.stack-head):not(.actions) a {
    display: inline-block; padding: 13px 6px; margin: -13px -6px;
  }
  .page-admin .check { min-height: 44px; gap: 12px; font-size: 15px; }
  .page-admin .check input, .page-admin .opt-list input[type="checkbox"] { width: 22px; height: 22px; flex: none; }
  .page-admin .opt-list label { min-height: 44px; font-size: 15px; }
  .page-admin .opt-search input { height: 44px; font-size: 16px; }
  .page-admin .field .help { font-size: 12.5px; }
  .page-admin .reveal-term { flex-wrap: wrap; }
  .page-admin .reveal-term code { flex: 1 1 100%; order: 2; font-size: 17px; }
  .page-admin .reveal-prompt { display: none; }
  .page-admin .reveal-copy { min-height: 44px; padding: 0 16px; font-size: 13px; margin-left: auto; order: 1; }

  /* filter bar: search on its own row, labelled fields full width, dates two-up */
  .page-admin .filter-primary { gap: 10px; padding: 12px; }
  .page-admin .filter-search { flex: 1 1 100%; max-width: none; }
  .page-admin .filter-search input { height: 44px; padding-left: 38px; }
  .page-admin .field-inline { flex: 1 1 calc(50% - 5px); min-width: 0; flex-direction: column; align-items: stretch; gap: 4px; }
  .page-admin .field-inline:has(> label) { flex-basis: 100%; }
  .page-admin .field-inline:has(> input[type=date]) { flex-basis: calc(50% - 5px); }
  .page-admin .field-inline select, .page-admin .field-inline input { width: 100%; min-width: 0; height: 44px; font-size: 16px; }
  .page-admin .field-inline > label { white-space: normal; }
  .page-admin .filter-primary > .btn { flex: 1 1 100%; height: 44px; }
  .page-admin .filterbar .row { padding: 12px; }
  .page-admin .filterbar .f { min-width: calc(50% - 8px); max-width: none; }
  .page-admin .pager { justify-content: space-between; }

  /* form action rows wrap and the buttons share the row; a long page's own Save bar stays
     in reach (sticky) — the user page is ~4,700px tall */
  .page-admin .form-actions { flex-wrap: wrap; gap: 10px; }
  .page-admin .form-actions .btn { flex: 1 1 140px; }
  .page-admin .form-actions .muted { flex: 1 1 100%; }
  .page-admin > form > .form-actions:last-child {
    position: sticky; bottom: 0; z-index: 3; margin: 14px -12px 0;
    padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
    background: rgba(255,255,255,.96); border-top: 1px solid var(--line-strong);
    box-shadow: 0 -6px 16px rgba(11,40,42,.08);
  }

  /* the Back-to-top button would sit on the Save bar; lift it clear */
  .page-admin:has(> form > .form-actions:last-child) ~ .to-top { bottom: 84px; }

  /* wide tables -> one labelled card per row. mobile.js copies each header onto its cell
     (data-label); the header row becomes a scrolling "sort by" strip when it has sort
     links, and disappears when it doesn't. .stack-head cells are the card's title,
     .stack-block cells take the full width under their label. */
  .page-admin .card-body.tight.tablewrap:has(> table.stack-sm) { overflow: hidden; }
  table.ledger.stack-sm, table.ledger.stack-sm tbody, table.ledger.stack-sm thead { display: block; width: 100%; }
  table.ledger.stack-sm thead:not(:has(th a)) { display: none; }
  table.ledger.stack-sm thead tr {
    display: flex; align-items: center; gap: 2px; padding: 0 8px; overflow-x: auto; scrollbar-width: none;
    background: var(--surface-2); border-bottom: 1px solid var(--line);
  }
  table.ledger.stack-sm thead tr::before {
    content: "Sort"; flex: none; padding: 0 8px 0 6px;
    font: 600 10.5px var(--f-display); text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint);
  }
  table.ledger.stack-sm thead th { display: none; }
  table.ledger.stack-sm thead th:has(a) { display: block; flex: none; padding: 0; border: 0; background: none; }
  table.ledger.stack-sm thead th a { min-height: 44px; padding: 0 10px; font-size: 11.5px; }
  table.ledger.stack-sm tbody tr {
    display: flex; flex-direction: column; gap: 1px; padding: 10px 14px 12px; border-bottom: 1px solid var(--line);
  }
  table.ledger.stack-sm tbody tr:last-child { border-bottom: 0; }
  /* label floats left, the value reads right-aligned beside/under it (a cell holds mixed
     text, pills and links, so it cannot be a flex row without splitting them apart) */
  table.ledger.stack-sm tbody td {
    display: flow-root; order: 1; min-width: 0; padding: 3px 0; border: 0; text-align: right;
    white-space: normal; background: none; overflow-wrap: anywhere;
  }
  table.ledger.stack-sm tbody td::before {
    content: attr(data-label); float: left; max-width: 45%; margin-right: 14px; text-align: left;
    font: 600 10.5px/22px var(--f-display); text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint);
  }
  table.ledger.stack-sm tbody td:not([data-label])::before { display: none; }
  table.ledger.stack-sm tbody td:empty, table.ledger.stack-sm tbody td[data-blank] { display: none; }
  table.ledger.stack-sm tbody td.stack-head {
    order: 0; display: block; padding: 0 0 4px; text-align: left; font-size: 16px; font-weight: 600;
  }
  table.ledger.stack-sm tbody td.stack-head::before { display: none; }
  table.ledger.stack-sm tbody td.stack-head > a { display: inline-block; padding: 10px 0; }
  table.ledger.stack-sm tbody td.stack-block { display: block; text-align: left; }
  table.ledger.stack-sm tbody td.stack-block::before { display: block; float: none; margin: 0 0 3px; max-width: none; }
  table.ledger.stack-sm tbody td.actions {
    order: 9; display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; padding-top: 8px;
  }
  table.ledger.stack-sm tbody td.actions::before { display: none; }
  table.ledger.stack-sm tbody td.actions a {
    display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 0 18px;
    font-size: 14px; border: 1px solid var(--line-strong); border-radius: 8px; background: var(--surface);
  }
  table.ledger.stack-sm tbody td.actions form { margin: 0; }
  table.ledger.stack-sm tbody tr.empty td { display: block; text-align: center; padding: 26px 14px; }
  table.ledger.stack-sm tbody tr.empty td::before { display: none; }
  table.ledger.stack-sm .audit-what { white-space: normal; }

  /* summary tiles (Active sessions, Sign-ins): tighter, so three of them do not fill a screen */
  .page-admin .stat { padding: 12px 16px; }
  .page-admin .stat-value { font-size: 28px; }
  .page-admin .stat-label { margin-top: 5px; }

  /* the one-time backup codes sheet: two columns (a code is 9 characters) in larger type */
  .page-admin .backup-codes { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .page-admin .backup-codes li { padding: 12px 10px; gap: 6px; }
  .page-admin .backup-codes code { font-size: 17px; letter-spacing: .04em; }
}
