:root {
  color-scheme: light;
  --border: #d8dee5;
  --text: #1f2933;
  --muted: #66717e;
  /* A visit with no caregiver assigned. Deliberately its own token rather
     than a mix of --text: in dark mode --text is near-white, so mixing it
     produced a PALE block -- the opposite of "dark grey" -- while looking
     correct in light mode. "Dark grey" means darker than the surface it sits
     on, which is a different colour in each theme. */
  --accent: #1565c0;
  --accent-dark: #0d47a1;
  --accent-gradient: linear-gradient(135deg, #1976d2, #0d47a1);
  --danger: #c62828;
  --bg-subtle: #eaf6fb;
  --radius: 10px;
  --radius-sm: 6px;
  --radius-lg: 14px;
  /* Being @-tagged is ORANGE, everywhere it shows: the row tag, the
     Messages nav count, the chat dot, the message bubble. It was the same
     green as "included", so the two states a reader most needs to tell
     apart -- somebody asked ME something, versus I am simply on this thread
     -- looked identical at a glance. Dark enough for white badge ink; a
     redder orange than --category-patient's gold, so those two stay
     distinct as well. */
  --mention: #c2410c;
  --mention-bg: #fdece2;
  /* The canvas <body> sits on -- kept separate from --surface (cards,
     rows, popovers) even though the two are the same white in light mode,
     so dark mode can tell "the page" and "a thing raised off the page"
     apart. Without this, redefining --surface for dark cards would have
     also repainted the page behind them the exact same colour, and every
     card would have lost its elevation. */
  --page-bg: #fff;
  /* Elevation scale -- matches fshc-nurse-portal's tokens so raised
     surfaces (cards, popovers, the sticky topbar, hovered rows/buttons)
     read consistently across both apps instead of homecare-app looking
     flatter. */
  --shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.05);
  --shadow-sm: 0 2px 8px rgba(15, 23, 42, 0.07);
  --shadow-md: 0 6px 20px rgba(15, 23, 42, 0.1);
  --shadow-lg: 0 16px 48px rgba(15, 23, 42, 0.18);
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);

  /* ---- Surfaces -------------------------------------------------------
     The stylesheet had grown eight near-identical off-whites (#fafbfc,
     #fbfbfc, #fafbfe, #f7f9fa, #f3f6fa, #f4f6f8, #f1f3f5, #f1f2f4) used
     interchangeably for the same three jobs. Three tokens, one job each. */
  --surface: #fff;            /* cards, table rows, popovers */
  --surface-subtle: #fafbfc;  /* zebra rows, table heads, inset panels */
  --surface-sunken: #f4f6f8;  /* the plane a card sits on */
  --surface-hover: #eef1f4;   /* hovered row / ghost button */
  --border-strong: #c3ccd6;   /* input borders, dividers that must read */

  /* ---- State scale ----------------------------------------------------
     ONE language for every state in the app. Before this, a status badge
     and a calendar chip said the same thing in different hues: "done" was
     #226b3a on a badge and #2e7d32 on the calendar, "missing" was #9a2c22
     / #c62828 / #b3261e depending on the page, and "entered by hand" was
     violet on the calendar but amber (status-warn) in the Visits table.

     Each role has three parts: the line/mark colour, a soft fill, and the
     ink that sits on that fill.

     Two notes on the colours themselves, both measured with the dataviz
     validator against this app's white surface rather than eyeballed:

     1. good(green) vs critical(red) is deutan dE 2.8 -- they are
        indistinguishable to a red-green colourblind reader, and no
        re-stepping fixes that while keeping red and green meaning bad and
        good. The mitigation is that a state is NEVER colour alone: every
        badge carries its text, and calendar chips carry a state glyph
        (see .cal-chip::before). Do not remove either.
     2. manual was #6a3fb5, which sat dE 12.1 from --accent blue in normal
        vision and 2.7 under deuteranopia -- the worst pair in the app, and
        gratuitous, since nothing forces "typed in by hand" to be blue-ish.
        Re-stepped to #8e3a9e: 17.9 normal (clears the >=15 floor), 7.0
        deutan (legal given the glyph + label above). */
  --state-good: #2e7d32;
  --state-good-soft: #e6f4ea;
  --state-good-ink: #226b3a;
  --state-warn: #c99700;
  --state-warn-soft: #fff3c4;
  --state-warn-ink: #6b4e00;
  --state-critical: #b3261e;
  /* Cancelled / no-show. Orange, not the critical red: a visit somebody
     recorded as cancelled is a known, closed outcome, while red is kept
     for the unstaffed visits that still need someone to act. They read as
     the same urgency otherwise, separated only by a dashed border. */
  --state-missed: #c2570b;
  --state-missed-soft: #fdeee2;
  --state-missed-ink: #8a3d06;
  --state-critical-soft: #fdecea;
  --state-critical-ink: #9a2c22;
  --state-info: #1565c0;
  --state-info-soft: #e8f0fe;
  --state-info-ink: #0f4c94;
  --state-neutral: #9aa5ad;
  --state-neutral-soft: #eceef0;
  --state-neutral-ink: #5b6470;
  --state-manual: #8e3a9e;
  --state-manual-soft: #f5eaf8;
  --state-manual-ink: #6b2a78;

  /* ---- Chart ink ------------------------------------------------------
     Categorical slots, for the one chart whose parts are identities rather
     than states (a bill's day/night/mileage/fees composition). Validated
     all-pairs on white: worst CVD dE 9.2, worst normal-vision dE 16.3.
     Four is the cap -- a fifth slot does not clear the floors. Anything
     state-shaped uses the state scale above, never these. */
  --chart-1: #2a78d6;
  --chart-2: #eb6834;
  --chart-3: #1baf7a;
  --chart-4: #4a3aa7;
  --chart-grid: #e6eaef;
  --chart-axis: #c3ccd6;
  --chart-track: #eef1f4;  /* the unfilled part of a bar or meter */

  /* Sequential blue, light->dark, for continuous magnitude (calendar
     density). Steps 100-700; an ordinal use starts no lighter than 250. */
  --seq-100: #e8f1fc;
  --seq-250: #b7d3f6;
  --seq-400: #6da7ec;
  --seq-550: #2a78d6;
  --seq-700: #14508f;

  /* Message categories -- was three independent hardcoded copies of the
     same four hex values (_messages_list.html's CATEGORY_COLORS dict,
     base.html's nav badges, _provider_nav.html's nav badges), which is
     exactly the drift risk the state scale above exists to prevent. Kept
     as their own axis rather than folded into --state-*: these identify
     WHICH category a thread belongs to, not a status. Values match
     fshc-nurse-portal's messages.html on purpose -- do not re-step them
     without checking that app too. tagged/included/audit happen to equal
     --mention/--accent/--muted already; aliased rather than duplicated. */
  --category-patient: #8f6a09;
  --category-tagged: var(--mention);
  /* Green, and its own value rather than an alias of --accent: "included"
     is now a colour in its own right (it used to be the same blue as every
     other accent in the app, which said nothing), and it is the green
     --mention gave up when tagging went orange. */
  --category-included: #1b7a3d;
  --category-audit: var(--muted);

  /* Ink for text sitting ON a solid state/accent fill -- a badge count, the
     active nav pill, a "today" day number. White in light mode, because
     those fills are dark there. In dark mode the fills are what lighten (an
     accent that stayed #1565c0 would vanish against the page), so the ink
     has to travel the other way or the pair inverts into unreadable
     white-on-pastel. Measured in a headless browser this time, which is the
     re-validation the dark state-scale note above asks for: every one of
     these was between 2.2:1 and 2.8:1. Fills that DON'T shift between
     themes keep a literal #fff on purpose. */
  --badge-ink: #fff;
  /* Form-builder field a recipient (not the agency) fills in. Its own
     blue, deliberately not --accent, so the two roles stay tellable apart. */
  --field-locked: #2563eb;
}

/* ---- Dark mode -----------------------------------------------------------
   Two copies of the same declarations on purpose, not a CSS bug: the media
   query covers "OS says dark, no explicit choice on this device", and the
   attribute selector covers the toggle in the topbar overriding that either
   way (an explicit light pick beating a dark OS, or vice versa). Nothing
   outside :root ever references dark mode directly -- every rule in this
   file already reads a variable, so redefining the variables here is the
   entire mechanism.

   Hues keep their light-mode identity; only lightness moves; toward the
   dark surface for the "soft" tint that used to be a light wash, and toward
   white for the "ink" that used to be a shade darker than the line color --
   the two roles trade places once the surface they sit on flips. Not
   re-validated against the dataviz tool the light state scale was (no
   headless renderer in this environment) -- if a pairing looks wrong once
   you can actually see it, that is the thing to fix, not the reasoning. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --border: #2b3440;
    --text: #e7ebf0;
    --muted: #93a0ad;
    --accent: #5b9bf0;
    --accent-dark: #3d7dd6;
    --accent-gradient: linear-gradient(135deg, #2f6fb8, #1e4a86);
    --danger: #e5675f;
    --bg-subtle: #17222c;
    --mention: #fb8b4c;
    --mention-bg: #2b1a10;
    --page-bg: #0d1117;

    --surface: #161b22;
    --surface-subtle: #1b2129;
    --surface-sunken: #10141a;
    --surface-hover: #212a34;
    --border-strong: #3a4552;

    --state-good: #57b860; --state-good-soft: #16281d; --state-good-ink: #8fdb9b;
    --state-warn: #e0b93f; --state-warn-soft: #2c2410; --state-warn-ink: #f0d585;
    --state-critical: #e5675f; --state-critical-soft: #2c1614; --state-critical-ink: #f2a29c; --state-missed: #f0904a; --state-missed-soft: #2e1d12; --state-missed-ink: #f5b98c;
    --state-info: #5b9bf0; --state-info-soft: #16222e; --state-info-ink: #a9c9f5;
    --state-neutral: #8b96a3; --state-neutral-soft: #232830; --state-neutral-ink: #c3ccd4;
  --state-manual: #c17ad1; --state-manual-soft: #271b2c; --state-manual-ink: #e2b3ec;

    --chart-1: #5b9bf0;
    --chart-2: #f08a5b;
    --chart-3: #3fd19c;
    --chart-4: #8f7ae0;
    --chart-grid: #2b3440;
    --chart-axis: #3a4552;
    --chart-track: #232830;

    --seq-100: #1a2733;
    --seq-250: #1f3550;
    --seq-400: #2a4e78;
    --seq-550: #3568a0;
    --seq-700: #5b9bf0;

    --category-patient: #d9a63e;
    --category-included: #4fbd7a;
  --badge-ink: #0b1220;
  --field-locked: #7ea9f5;
    --badge-ink: #0b1220;
    --field-locked: #7ea9f5;
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --border: #2b3440;
  --text: #e7ebf0;
  --muted: #93a0ad;
  --accent: #5b9bf0;
  --accent-dark: #3d7dd6;
  --accent-gradient: linear-gradient(135deg, #2f6fb8, #1e4a86);
  --danger: #e5675f;
  --bg-subtle: #17222c;
  --mention: #fb8b4c;
  --mention-bg: #2b1a10;
  --page-bg: #0d1117;

  --surface: #161b22;
  --surface-subtle: #1b2129;
  --surface-sunken: #10141a;
  --surface-hover: #212a34;
  --border-strong: #3a4552;

  --state-good: #57b860; --state-good-soft: #16281d; --state-good-ink: #8fdb9b;
  --state-warn: #e0b93f; --state-warn-soft: #2c2410; --state-warn-ink: #f0d585;
  --state-critical: #e5675f; --state-critical-soft: #2c1614; --state-critical-ink: #f2a29c; --state-missed: #f0904a; --state-missed-soft: #2e1d12; --state-missed-ink: #f5b98c;
  --state-info: #5b9bf0; --state-info-soft: #16222e; --state-info-ink: #a9c9f5;
  --state-neutral: #8b96a3; --state-neutral-soft: #232830; --state-neutral-ink: #c3ccd4;
  --state-manual: #c17ad1; --state-manual-soft: #271b2c; --state-manual-ink: #e2b3ec;

  --chart-1: #5b9bf0;
  --chart-2: #f08a5b;
  --chart-3: #3fd19c;
  --chart-4: #8f7ae0;
  --chart-grid: #2b3440;
  --chart-axis: #3a4552;
  --chart-track: #232830;

  --seq-100: #1a2733;
  --seq-250: #1f3550;
  --seq-400: #2a4e78;
  --seq-550: #3568a0;
  --seq-700: #5b9bf0;

  --category-patient: #d9a63e;
  --category-included: #4fbd7a;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--text);
  background: var(--page-bg);
}

a { color: var(--accent); text-decoration: none; transition: color 0.15s var(--ease); }
a:hover { text-decoration: underline; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 999px; border: 2px solid var(--page-bg); }
::-webkit-scrollbar-thumb:hover { background: var(--muted); }

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  /* color-mix, not a raw rgba(255,255,255,...) literal -- that was opaque
     to the dark-mode token swap below (a hardcoded white blended right
     into white --text on top of it, the single worst offender in the
     "white on white" report). Deriving the translucency from --surface
     means it goes dark for free whenever --surface does. */
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: saturate(180%) blur(8px);
  -webkit-backdrop-filter: saturate(180%) blur(8px);
  box-shadow: var(--shadow-xs);
  z-index: 10;
}

.topbar-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--text);
}
.topbar-brand:hover { text-decoration: none; }

.topbar-logo-img {
  width: 26px;
  height: 26px;
  border-radius: 7px;
  object-fit: contain;
  flex-shrink: 0;
}

.topbar-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--accent);
  color: var(--badge-ink);
  font-size: 1rem;
  font-weight: 700;
  line-height: 1;
}

.topbar-actions { display: flex; align-items: center; gap: 12px; }

.topbar-link {
  font-size: 0.9rem;
  padding: 6px 4px;
  border-bottom: 2px solid transparent;
  transition: color 0.15s, border-color 0.15s;
}
.topbar-link:hover { text-decoration: none; color: var(--accent); }
.topbar-link.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }

/* "Log out" is a POST form rather than a link (see app/routers/auth.py), so
   the trigger has to be a <button>. These two rules strip the native button
   chrome and let it inherit the topbar's link styling, so the change is
   invisible in the bar itself. */
.topbar-logout-form { display: contents; }
.topbar-link-button {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--surface-hover);
  color: var(--text);
  font-size: 0.75rem;
  font-weight: 700;
  text-decoration: none;
}

a.avatar:hover { background: var(--border-strong); }

.page { width: 90%; max-width: 1600px; margin: 0 auto; padding: 16px; }

.flash {
  background: var(--bg-subtle);
  border: 1px solid color-mix(in srgb, var(--accent), var(--surface) 60%);
  border-radius: var(--radius);
  padding: 12px 14px;
  margin-bottom: 16px;
  font-size: 0.9rem;
}

.visit-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 14px;
  box-shadow: 0 1px 2px rgba(15, 23, 32, 0.04);
  transition: box-shadow 0.15s, border-color 0.15s;
}
.visit-card:hover { box-shadow: 0 4px 14px rgba(15, 23, 32, 0.08); border-color: #c3ccd6; }

.visit-card-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px 10px;
  margin-bottom: 6px;
}

.visit-card-patient { font-weight: 600; min-width: 0; overflow-wrap: break-word; }

.visit-card-action { font-size: 0.9rem; font-weight: 600; }
.visit-card-action.status-submitted { color: var(--muted); }
.visit-card-action.status-not_started,
.visit-card-action.status-in_progress { color: var(--danger); }

.visit-card-date { color: var(--muted); font-size: 0.85rem; margin-bottom: 8px; }

.visit-card-rows {
  border-top: 1px solid var(--border);
  padding-top: 8px;
  font-size: 0.9rem;
}

.visit-card-row {
  display: flex;
  justify-content: space-between;
  padding: 3px 0;
}

.visit-card-row .label { color: var(--muted); }

.empty-state {
  text-align: center;
  color: var(--muted);
  padding: 40px 16px;
}

/* Baseline box for every text-ish input/select/textarea in the app, even
   ones that aren't inside a form.stacked/.search-bar (e.g. the inline
   add-item/notes forms embedded directly in a table cell on the Care
   Profile tab) -- without this they fall back to the browser's bare
   unstyled control, which looks visibly out of place next to every other
   field in the app. form.stacked/.search-bar/.dense below are more
   specific selectors so they still win their own spacing/sizing on top
   of this regardless of source order. */
input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=time], input[type=tel], input[type=search],
select, textarea {
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 0.92rem;
  font-family: inherit;
  color: var(--text);
  background: var(--surface);
}
textarea { resize: vertical; }

form.stacked label {
  display: block;
  font-size: 0.82rem;
  color: var(--muted);
  margin-bottom: 2px;
}

form.stacked input[type=text],
form.stacked input[type=email],
form.stacked input[type=password],
form.stacked input[type=number],
form.stacked input[type=date],
form.stacked input[type=time],
form.stacked select,
form.stacked textarea {
  width: 100%;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.93rem;
  margin-bottom: 4px;
  font-family: inherit;
}

form.stacked textarea { min-height: 58px; resize: vertical; }

form.stacked input[type=file] {
  display: block;
  width: 100%;
  padding: 10px 0;
  margin-bottom: 6px;
  font-size: 0.9rem;
}

/* Modifier for form.stacked: lays label+field pairs into a 2-column grid
   instead of one full-width field per row, for forms with many short
   fields (e.g. Add a patient) where full-width rows waste space. */
form.stacked.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 14px;
  align-items: start;
}
form.stacked.form-grid > .grid-span-2 { grid-column: 1 / -1; }
/* Three columns for the intake forms, which ask for everything at once:
   at two, "add a patient" ran to a screen and a half and the sections that
   organise it were further apart than the fields they group. Same span
   helper works in both (1 / -1), so a full-width row stays full width. */
form.stacked.form-grid-3 { grid-template-columns: repeat(3, 1fr); }
/* A section heading inside an intake form. It is the thing that makes a
   long form readable -- twenty boxes in a grid are twenty boxes; the same
   twenty under four headings are four questions. */
.form-section-heading {
  grid-column: 1 / -1;
  font-weight: 600;
  font-size: 0.78rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 12px 0 2px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}
.form-section-heading:first-of-type { margin-top: 2px; padding-top: 0; border-top: none; }
.form-section-heading > .help-text { text-transform: none; letter-spacing: 0; font-weight: normal; display: block; margin: 2px 0 0; }
/* Four across for the longest intake form (add a patient: 27 fields). At three
   it ran past a screen and a half; the fields are short -- names, a phone, a
   date -- so a fourth column costs nothing in legibility and takes about two
   rows off the height. Steps down to three before it gets cramped. */
form.stacked.form-grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 1180px) {
  form.stacked.form-grid-4 { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 900px) {
  form.stacked.form-grid-3 { grid-template-columns: 1fr 1fr; }
  form.stacked.form-grid-4 { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 640px) {
  form.stacked.form-grid,
  form.stacked.form-grid-3,
  form.stacked.form-grid-4 { grid-template-columns: 1fr; }
}

/* Further modifier, stack with .form-grid: shrinks the airy default
   spacing (10px/12px padding, 6px margins) for boxes with many short
   fields where that whitespace adds up to real height (e.g. Create Bill). */
form.stacked.dense label { margin-bottom: 2px; font-size: 0.8rem; }
form.stacked.dense input[type=text],
form.stacked.dense input[type=email],
form.stacked.dense input[type=number],
form.stacked.dense input[type=date],
form.stacked.dense input[type=time],
form.stacked.dense select {
  padding: 7px 10px;
  margin-bottom: 4px;
}
form.stacked.dense.form-grid { column-gap: 10px; }

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 6px;
  padding: 8px 4px;
  min-height: 44px;
  cursor: pointer;
}
.checkbox-row input {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  accent-color: var(--accent);
}
.checkbox-row label { margin: 0; cursor: pointer; }
/* A provider who's actually been on this patient (see
   people_for_thread_patient) -- tinted and sorted to the top of the
   Providers panel on the new-thread form, matching fshc-nurse-portal. */
.checkbox-row.suggested-participant { background: var(--bg-subtle); border-radius: 8px; }

/* .btn-secondary is included here (not just its own rule below) because
   it's used on both real <button> elements (which already match "button"
   below regardless of class) and plain <a class="btn-secondary"> links
   (e.g. every "Reset filters"/pagination link) -- an <a> doesn't match
   "button", so without this it would silently miss the shared box model
   (display/padding/radius/min-height/shadow) that makes it look like an
   actual button instead of bordered inline text. */
button, .btn, .btn-secondary {
  display: inline-block;
  background: var(--accent-gradient);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 13px 18px;
  min-height: 44px;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  text-align: center;
  box-shadow: var(--shadow-xs);
  transition: opacity 0.15s var(--ease), transform 0.1s var(--ease), box-shadow 0.15s var(--ease);
}

/* Safety net for the rule above. `display: inline-block` there is an AUTHOR
   rule, and `[hidden] { display: none }` is a USER-AGENT rule -- author wins
   regardless of specificity, so setting `el.hidden = true` on a <button> did
   nothing at all. Two places relied on it and silently failed: the /apply
   wizard, where Back and "Submit application" showed on every step including
   the first, and the row popovers' "Start editing", which stayed on screen
   after being clicked. Declared !important so no later display rule can
   re-open the same hole. */
[hidden] { display: none !important; }
button:hover, .btn:hover { opacity: 0.95; text-decoration: none; box-shadow: var(--shadow-sm); transform: translateY(-1px); }
button:active, .btn:active { transform: scale(0.98) translateY(0); box-shadow: var(--shadow-xs); }
button:disabled, .btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; }

/* Clock times on /admin/visits-notes double as the control that corrects
   them. The office reaches for a wrong time far more often than for an icon
   at the far end of the row, and the time is the thing they are already
   looking at when they notice it is wrong.

   Deliberately NOT styled as a button: it is a readout first and a control
   second, so it keeps the cell's own type and colour (including the amber a
   still-open visit gets from .visit-time-open) and carries a dotted
   underline, the long-standing convention for "this text is editable".
   Everything here is undoing the shared button box model above. */
.time-adjust-btn {
  display: inline;
  background: none;
  border: none;
  border-bottom: 1px dotted var(--border-strong);
  border-radius: 0;
  padding: 0;
  min-height: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  box-shadow: none;
  cursor: pointer;
}
.time-adjust-btn:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
  opacity: 1;
  box-shadow: none;
  transform: none;
}
.time-adjust-btn:active { transform: none; box-shadow: none; }

/* Wide enough for "12:00 AM - 7:40 AM" plus the "logged ..." line under a
   corrected visit without either wrapping, which is what was squeezing the
   two lines together and pushing EVV/Note in against them. */
table.admin-table th.col-time,
table.admin-table td[data-label="Time"] { min-width: 172px; }
button:focus-visible, .btn:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.btn-secondary {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  box-shadow: none;
}
.btn-secondary:hover { background: var(--surface-subtle); border-color: var(--border-strong); box-shadow: var(--shadow-xs); }

.btn-danger { background: var(--danger); color: var(--badge-ink); }

.btn-small { min-height: 36px; padding: 8px 12px; font-size: 0.85rem; }

/* Compact per-row action button -- an emoji icon plus a `title` tooltip,
   no visible label, ported from fshc-nurse-portal's own table-action
   convention (Edit/Delete/Reset password/Calendar/etc. on patient/
   provider/admin list rows). Sizing/shape lives here for every variant;
   color/background comes from whatever other class is paired with it
   (.btn-secondary, .btn-danger, or -- a bare <button class="icon-btn">
   with neither -- the transparent/borderless look below). */
/* The drawn icons (app/icons.py: pdf, csv, view, edit). Sized against 1em
   rather than a fixed px, because the same {{ icon('edit') }} has to work
   alone inside a 30px .icon-btn AND inline in a 0.8rem status badge
   ("{{ icon('edit') }} Manual" on visits_notes.html) -- a px size would be
   right in one place and wrong in the other.

   1.15em, not 1em: an emoji glyph draws at roughly 1.15x its font-size, so a
   1em SVG sits visibly small beside the emoji icons it shares a row with.
   Matched by eye against the wastebasket at .icon-btn's 0.95rem.

   The -0.27em baseline shift is the matching half of that: an inline SVG sits
   ON the baseline by default, which hangs it high against text. */
.ic {
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.27em;
  flex-shrink: 0;
}
/* Inside an icon-only button there is no text to align to, and the flex
   centring already handles position -- so drop the shift, which otherwise
   pushes the glyph a quarter-em below centre. */
.icon-btn > .ic { vertical-align: 0; }

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  min-height: 30px;
  padding: 0;
  font-size: 0.95rem;
  line-height: 1;
  border-radius: 6px;
  text-decoration: none;
  position: relative;
  flex-shrink: 0;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;
}
/* :not(.icon-btn-danger) matters. This selector is (0,4,1); the
   `.icon-btn-danger { color: var(--danger) }` rule below is (0,1,0), so
   without the exclusion this grey wins on every <button> and the danger
   variant only ever coloured <a> elements -- which is why the destructive
   icon buttons (the "delete for everyone" control in _messages_list.html,
   and Deny on the dashboard's schedule requests) were rendering the same
   grey as the harmless ones. Measured in the browser, not read off the
   file: both computed to rgb(102,113,126). */
button.icon-btn:not(.btn-secondary):not(.btn-danger):not(.icon-btn-danger):not(.icon-btn-approve) {
  background: transparent;
  border: none;
  color: var(--muted);
  box-shadow: none;
}
button.icon-btn:not(.btn-secondary):not(.btn-danger):not(.icon-btn-danger):hover { background: var(--bg-subtle); opacity: 1; box-shadow: none; transform: none; }
button.icon-btn.icon-btn-danger { background: transparent; border: none; box-shadow: none; }
/* The approving half of an approve/deny pair. Same exclusion list as the
   grey rule above, for the same reason: that selector is (0,4,1) and would
   otherwise win on every <button>, which is how the tick and the cross ended
   up the same grey. */
button.icon-btn.icon-btn-approve { background: transparent; border: none; box-shadow: none; }
.icon-btn-approve { color: var(--state-good); }
.icon-btn-danger { color: var(--danger); }

/* The message icon on a visit row (admin/_visit_message_link.html).
   It needs its own rule because it is an <a>, not a <button>: it navigates
   to the compose form. Every treatment above -- the grey, the hover, the
   removal of the button chrome -- is scoped to `button.icon-btn`, so an
   anchor carrying that class got the 30px box and nothing else, and
   rendered as a bare blue link glyph. Giving it `btn-secondary btn-small`
   instead went the other way: a full button competing with Save and Deny
   for the eye, for what is the least destructive control in the row.
   So: same box and same muted grey as its <button> neighbours, and the
   same hover, with none of the button chrome. */
a.visit-message-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  min-height: 30px;
  padding: 0;
  font-size: 0.95rem;
  line-height: 1;
  border-radius: 6px;
  background: transparent;
  border: none;
  box-shadow: none;
  color: var(--muted);
  text-decoration: none;
  flex-shrink: 0;
  cursor: pointer;
  /* The glyph is an emoji (envelope + variation selector), painted by the
     font rather than by `color` -- so no desaturating it at rest: its
     neighbours in the past-visits strip are full-colour emoji (the eye, the
     download arrow) and a greyed envelope beside them reads as disabled.
     Hover is carried by the background alone, exactly as it is for those. */
  transition: background 0.15s ease;
}
a.visit-message-link:hover,
a.visit-message-link:focus-visible {
  background: var(--bg-subtle);
  text-decoration: none;
}
.table-actions { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 4px; align-items: center; }

/* A row-action cluster whose leading control is CONDITIONAL -- Transmit on a
   claim that has not gone yet, Send on a draft invoice -- followed by controls
   every row has. Packed left, the fixed ones landed at a different x on every
   row, so scanning the column meant re-finding the same icon on each line.
   Anchoring the cluster to the right edge of its cell puts the always-present
   controls in one place down the column, whatever precedes them.

   Right rather than fixed-width slots on purpose: a slot has to be as wide as
   the widest label it might hold, which is a number to maintain per table and
   wrong the first time somebody adds a longer one. */
.table-actions-end {
  display: flex;
  gap: 6px;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: nowrap;
  margin-top: 4px;
}
.table-actions-end > form { margin: 0; }

/* A row action this particular row cannot use. It is still rendered and still
   takes its space, so the controls beside it do not slide sideways when it is
   absent -- .table-actions-end alone only holds the LAST control still.
   visibility rather than display:none on purpose: the box keeps its size, so
   no slot width has to be measured against a label or kept in step with one.
   It also drops out of the tab order, which is why the markup pairs this with
   disabled/aria-hidden rather than relying on either alone. */
.slot-empty { visibility: hidden; }

/* Row-action clusters that have outgrown a single line -- the patient,
   nurse and caregiver account rows each carry six or seven icons, which as
   one row pushed the actions column wider than everything it sat beside.
   Grid rather than a wrapped flex line with a max-width: the icon count per
   row varies (billing is permission-gated, the document-issue warning only
   appears when there is one), and a width that happens to break 7 icons
   into two lines breaks 5 into one. Filling down the two rows first and
   adding a column per pair keeps it exactly two rows at any count. */
.table-actions-2row {
  display: grid;
  grid-template-rows: repeat(2, auto);
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  gap: 2px 4px;
  justify-content: start;
  align-items: center;
  margin-top: 4px;
  width: max-content;
}
/* The edit/info popovers sit inside this cluster as siblings of the buttons
   that open them. They stay out of the grid on their own -- display:none
   until opened, position:fixed once open (.visit-popover-panel) -- so they
   never claim a cell. */

.form-section {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 12px;
  margin-bottom: 10px;
  box-shadow: var(--shadow-xs);
  transition: box-shadow 0.15s var(--ease);
}

/* /settings -- lay its (up to 10, for a super admin) independent cards
   into a newspaper-style multi-column flow instead of one long single
   column, so the page reads as a compact overview instead of a lot of
   scrolling for what's mostly small, unrelated forms. break-inside:avoid
   keeps a card from splitting across columns; margin-bottom (not the
   default .form-section one, which columns collapse oddly) spaces cards
   within a column. */
.settings-columns {
  column-count: 3;
  column-gap: 16px;
}
.settings-columns > .form-section {
  break-inside: avoid;
  display: inline-block;
  width: 100%;
  margin-bottom: 16px;
}
@media (max-width: 1100px) {
  .settings-columns { column-count: 2; }
}
@media (max-width: 640px) {
  .settings-columns { column-count: 1; }
}

.form-section h3 {
  margin: 0 0 8px;
  font-size: 0.95rem;
}

.section-card { padding: 0; overflow: hidden; }
.section-card summary {
  cursor: pointer;
  padding: 14px 16px;
  font-weight: 600;
  font-size: 1rem;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 44px;
}
.section-card summary::-webkit-details-marker { display: none; }
.section-card summary::after {
  content: "";
  width: 10px;
  height: 10px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(-45deg);
  transition: transform 0.15s;
  flex-shrink: 0;
  margin-left: 12px;
}
.section-card[open] summary::after { transform: rotate(45deg); }
.section-card-body { padding: 0 16px 16px; }

.section-toggle-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}

.sticky-action-bar {
  position: sticky;
  bottom: 0;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 12px 0;
  padding-bottom: calc(12px + env(safe-area-inset-bottom));
  margin-top: 8px;
  display: flex;
  gap: 10px;
  z-index: 5;
}
.sticky-action-bar button { flex: 1; }

.locked-banner {
  background: var(--state-warn-soft);
  border: 1px solid var(--state-warn);
  border-radius: var(--radius);
  padding: 12px 14px;
  margin-bottom: 16px;
  font-size: 0.9rem;
}

table.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
  margin-bottom: 12px;
}
table.admin-table th, table.admin-table td {
  text-align: left;
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  line-height: 1.3;
  vertical-align: top;
  /* Blanket, not per-column: tabular-nums only changes how DIGITS render
     (fixed width instead of proportional), so it's a no-op on any cell
     that has none. Cheaper and more complete than hunting down every
     money/count column by hand -- which had only caught 2 of roughly 8
     such columns before this. */
  font-variant-numeric: tabular-nums;
}
table.admin-table th {
  color: var(--muted);
  font-weight: 700;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
  border-bottom: 2px solid var(--border);
}
table.admin-table tbody tr:hover { background: var(--surface-hover); }

/* /admin/visits-notes renders one <table> per day rather than one table
   for the whole page (so the date-band head can sit between them) --
   table-layout:auto would size each one off only that day's own content,
   which is what made the columns visibly shift between days. Fixed
   layout plus the matching colgroup in the template pins identical
   widths across every day's table; cells still need to be told to wrap
   instead of overflow, since fixed layout no longer grows a column to
   fit a long unbroken value.

   Desktop only (min-width:1025px, above the tablet breakpoint below):
   the tablet rule deliberately overrides .admin-table to width:max-content
   so a cramped viewport doesn't squeeze every column to illegible text,
   which would fight a fixed layout here. At that width this page already
   gets the same horizontal-scroll treatment as every other admin table,
   so the per-day shift is far less noticeable there in practice. */
@media (min-width: 1025px) {
  table.visits-notes-table { table-layout: fixed; }
  table.visits-notes-table td { overflow-wrap: break-word; }
  /* The three narrow status columns hold short, fixed-shape values -- a time
     range, a badge, a badge. Wrapping them mid-value to save a few pixels
     makes them harder to scan than the space it recovers is worth. */
  table.visits-notes-table td:nth-child(3),
  table.visits-notes-table td:nth-child(4),
  table.visits-notes-table td:nth-child(5) { white-space: nowrap; }
  table.visits-notes-table td:last-child { white-space: nowrap; }
}

.table-scroll {
  overflow-x: auto;
  /* -webkit-overflow-scrolling: touch was here -- a legacy WebKit property
     for momentum scrolling on old iOS (<13), unnecessary on any modern
     device (momentum scrolling is the default now). Removed 2026-08-25:
     it has a well-documented side effect of creating its own compositing
     context, which was trapping the row-level .visit-popover-panel popups
     nested inside these tables (patient-edit-popover, etc.) -- they're
     position:fixed and meant to escape to the viewport/render above the
     global backdrop, but got stuck compositing inside THIS container
     instead. That's what every earlier attempt at this bug (position,
     pointer-events, translateZ, service-worker cache) was missing -- the
     panel and backdrop relationship was fine, the panel was trapped by an
     unrelated ancestor. Only popovers nested inside a .table-scroll table
     were ever affected, which matches what was actually reported. */
  margin-bottom: 4px;
}
.table-scroll table.admin-table { margin-bottom: 0; }

/* Free-text table cells (email, address, notes, ...) can otherwise run to
   several lines and blow out row height -- clamp to 2 lines with an
   ellipsis so a row stays narrow regardless of how long the underlying
   value is (the full value is still one click away via Edit).
   Applied to a <span> wrapping the cell's content, NOT the <td> itself:
   `display: -webkit-box` overrides a <td>'s table-cell display, which
   breaks its row-border rendering (the cell's own border collapses to
   the clamp box's shrink-to-fit content width instead of the full
   column width, making every row look double-bordered/misaligned). */
.wrap-cell {
  max-width: 260px;
  white-space: normal;
  word-break: break-word;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Below ~640px, responsive-table rows become stacked cards instead of a
   horizontally-scrolling table -- much easier to read one-handed on a phone.
   Requires each <td> to carry a data-label attribute. */
@media (max-width: 640px) {
  table.responsive-table thead { display: none; }
  table.responsive-table, table.responsive-table tbody,
  table.responsive-table tr, table.responsive-table td { display: block; width: 100%; }
  table.responsive-table tr {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 12px;
    margin-bottom: 12px;
  }
  table.responsive-table td {
    border: none;
    padding: 6px 0;
    display: flex;
    justify-content: space-between;
    gap: 12px;
    text-align: right;
  }
  /* Cells holding a LIST of items (Documents, Checklist, Forms, Assigned
     Providers, ...) render each item as its own top-level <div>/<form> --
     that's multiple flex children in the row layout above, which squeezes
     them all onto one unwrapped horizontal line instead of letting each
     stack on its own line (found on a real device, 2026-08-25: a
     patient's document badges ran off the right edge of the screen).
     :has() with 2+ direct children reliably distinguishes these
     multi-item cells from simple single-value ones (Name, Phone, ...)
     without needing every template call site to opt in with a class. */
  table.responsive-table td:has(> :nth-child(2)) {
    display: block;
    text-align: left;
  }
  table.responsive-table td:has(> :nth-child(2))::before {
    display: block;
    margin-bottom: 4px;
  }
  table.responsive-table td::before {
    content: attr(data-label);
    font-weight: 600;
    color: var(--muted);
    font-size: 0.78rem;
    text-align: left;
    flex-shrink: 0;
  }
  table.responsive-table td:empty,
  table.responsive-table td[data-label]:not([data-label=""]):has(> *:only-child:empty) { display: none; }

  /* Action cells (no label -- buttons/forms) stack full-width instead of
     sitting in the label/value flex row. */
  table.responsive-table td[data-label=""] {
    display: block;
    text-align: left;
  }
  table.responsive-table td[data-label=""]::before { content: none; }
  table.responsive-table td[data-label=""] form { margin-bottom: 6px; }
  table.responsive-table td[data-label=""] form:last-child { margin-bottom: 0; }
  table.responsive-table td[data-label=""] > form > button,
  table.responsive-table td[data-label=""] > details > summary { width: 100%; }

  /* iOS Safari auto-zooms the page when a focused input/select computes
     under 16px -- these all sit below that on the compact desktop sizing. */
  .search-bar input[type=search], .search-bar input[type=text], .search-bar input[type=date], .search-bar select {
    font-size: 16px;
  }
}

.help-text { color: var(--muted); font-size: 0.8rem; margin-top: 0; margin-bottom: 10px; }

.top-actions { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }

body:has(.login-wrap) {
  background: linear-gradient(180deg, var(--surface-sunken) 0%, var(--page-bg) 320px);
  min-height: 100vh;
}
.login-wrap {
  max-width: 380px;
  margin: 72px auto;
  padding: 32px 28px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.login-wrap h1 {
  font-size: 1.15rem;
  margin: 0 0 24px;
  text-align: center;
  letter-spacing: -0.01em;
}
.login-wrap form.stacked button { width: 100%; margin-top: 6px; }
.login-wrap p { text-align: center; }

/* A login failure is a critical-state alert, not a coloured caption --
   .flash's default blue/info tone would say the opposite of what this
   text means. Used in exactly two places (this page, signup_invalid.html),
   both already alert-shaped, so upgrading it here is safe everywhere it
   appears. */
.error-text {
  background: var(--state-critical-soft);
  color: var(--state-critical-ink);
  border: 1px solid var(--state-critical);
  border-radius: var(--radius);
  padding: 10px 12px;
  font-size: 0.88rem;
  margin-bottom: 14px;
}

.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 32, 0.55);
  align-items: center;
  justify-content: center;
  padding: 16px;
  z-index: 1000;
}
.modal-box {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 22px 20px;
  max-width: 380px;
  width: 100%;
  box-shadow: var(--shadow-lg);
}
.modal-box h3 { margin: 0 0 10px; font-size: 1.05rem; }
.modal-box p { margin: 0 0 18px; line-height: 1.4; }
.modal-actions { display: flex; gap: 10px; flex-direction: column-reverse; }
.modal-actions button { width: 100%; }

.sort-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 14px;
  font-size: 0.9rem;
}
.sort-bar a { font-weight: 600; }

/* A control that selects a window rather than narrowing a list: same
   look as a search bar, but no Reset, because there is no cleared
   state to go back to. Kept as its own class so the search-bar reset
   rule stays absolute. */
.period-picker { display: flex; gap: 8px; align-items: center; margin: 0; }

/* A range picker -- "the last 30 days / 3 months / year" -- laid out like a
   filter bar and deliberately NOT called one. A filter hides rows and can
   strand somebody in a subset, which is why every .search-bar must carry a
   reset (tests/test_ui_consistency.py). A range always shows something and
   has a default, so a reset control would be a button that undoes nothing. */
.range-bar,
.search-bar {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
  flex-wrap: wrap;
  align-items: center;
}
/* Every control in a filter bar -- search box, <select>, date picker --
   shares one compact size instead of each falling back to its own native
   browser sizing (a <select>/<input type=date> with no explicit styling
   renders taller/wider than a styled search box, so a filter row ends up
   with controls of visibly different heights). Narrow and uniform on
   purpose: these are filters, not the main content. Ported from
   fshc-nurse-portal's style.css for a consistent look between the two apps. */
.range-bar select,
.search-bar input[type=search],
.search-bar input[type=text],
.search-bar input[type=date],
.search-bar select {
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.85rem;
  font-family: inherit;
  height: 34px;
  box-sizing: border-box;
  background: var(--surface);
}
.search-bar input[type=search],
.search-bar input[type=text] {
  flex: 1 1 160px;
  min-width: 140px;
}
/* Opt-in, because the compact <select> above is deliberate: a filter row with
   four narrow dropdowns (Finance, Visits & Notes) should not have one of them
   swallow the row. But where a dropdown is the ONLY control in the bar --
   EHR Import's Visits tab, whose patient picker sits alone where the other
   two tabs have a full-width search box -- leaving it at its natural width
   puts a small control against a long strip of dead space, and the three
   tabs stop looking like the same page. */
.search-bar select.search-bar-wide {
  flex: 1 1 160px;
  min-width: 140px;
  /* max-width:none matters as much as the flex. The general
     `.search-bar select` rule below caps every filter dropdown at 220px, and
     a cap beats flex-grow -- growing to fill a row it is forbidden to be
     wider than does nothing. Without this the select moved about 40px and
     looked like the change had not worked. */
  max-width: none;
}
.search-bar select {
  flex: 0 1 auto;
  max-width: 220px;
}
.search-bar input[type=date] {
  flex: 0 0 auto;
  width: 148px;
}

.status-badge {
  display: inline-block;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.status-badge.status-not_started { background: var(--state-critical-soft); color: var(--state-critical-ink); }
/* Cancelled or a no-show: a closed, recorded outcome, so it takes the orange
   missed colour rather than the red the calendar keeps for work still needing
   someone to act. Matches .cal-swatch.cal-missed and the visit blocks. */
.status-badge.status-missed { background: var(--state-missed-soft); color: var(--state-missed-ink); }
.status-badge.status-in_progress { background: var(--state-info-soft); color: var(--state-info-ink); }
.status-badge.status-submitted { background: var(--state-good-soft); color: var(--state-good-ink); }
.status-badge.status-neutral { background: var(--state-neutral-soft); color: var(--state-neutral-ink); }
.status-badge.status-warn { background: var(--state-warn-soft); color: var(--state-warn-ink); }
/* "Entered by hand". The Visits table used to call this status-warn (amber),
   which said "something is wrong"; a hand-entered visit is a different
   METHOD, not a fault, and the amber put it in the same bucket as a real EVV
   gap. (The calendar no longer distinguishes it at all -- see
   visit_calendar_states -- so this is the one place the method is coloured.) */
.status-badge.status-manual { background: var(--state-manual-soft); color: var(--state-manual-ink); }

/* One 100%-wide bar, one segment per status/category, width proportional
   to count -- for a total that's more useful seen as a shape ("most of
   these are stuck in one place") than read as a sentence of numbers. Set
   each segment's share with the `flex` shorthand (not flex-grow alone --
   the shorthand also zeroes flex-basis, which is what lets a genuinely
   0-count segment collapse to nothing instead of claiming a sliver).
   Segment colours are the caller's choice via inline style (usually the
   same --state-* tokens status-badge uses, so the bar and any badges
   naming the same statuses always agree); .stacked-bar itself only owns
   the shape. Renders nothing readable when everything is empty -- callers
   guard the whole bar behind "is there any total" and fall back to their
   existing empty state, same as an empty table would. */
.stacked-bar {
  display: flex;
  height: 10px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--chart-track);
  margin: 8px 0 4px;
}
.stacked-bar-seg { flex-shrink: 0; }
.stacked-bar-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  font-size: 0.8rem;
  color: var(--muted);
  margin: 0 0 10px;
}
.stacked-bar-legend-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 5px;
  vertical-align: middle;
}
.stacked-bar-legend strong { color: var(--text); font-variant-numeric: tabular-nums; }

/* A single "N of M done" completion meter -- same track token as
   .stacked-bar, one fill instead of several segments. Deliberately no
   fixed height here (unlike .stacked-bar): this shows up both full-width
   in a dashboard card and as a compact inline sliver next to a status
   badge in a dense table row, so height is the caller's call. */
.progress-bar {
  border-radius: 999px;
  overflow: hidden;
  background: var(--chart-track);
  height: 6px;
}
.progress-bar-fill { height: 100%; background: var(--state-good); border-radius: inherit; }
/* Compact variant -- inline next to a status badge in a dense table row,
   where a full 6px bar at row-width would dominate the row instead of
   supplementing the badge. */
.progress-bar-sm { height: 4px; width: 46px; display: inline-block; vertical-align: middle; margin-left: 6px; }

/* Weekly hours -- one day/night stacked column per week. .weekly-hours-col
   is [total number, bar, week label] top to bottom; the row's own
   align-items:flex-end is what bottom-aligns every bar to a shared
   baseline even though their heights differ (a taller bar just pushes its
   own total-number label higher, the baseline and the week-label under it
   stay put). */
.weekly-hours-chart {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  margin: 8px 0 4px;
}
.weekly-hours-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  min-width: 0;
}
.weekly-hours-bar {
  width: 100%;
  max-width: 30px;
  display: flex;
  flex-direction: column;
  border-radius: 3px 3px 0 0;
  overflow: hidden;
  background: var(--chart-track);
}
/* chart-1 (blue) against chart-2 (orange), not chart-4 (blue-purple). Day and
   night stack as adjacent segments that can be a few pixels tall inside a
   64px bar, and blue-on-blue-purple is the closest pair in the palette -- the
   first to collapse at that size and the first to go under deuteranopia.
   Orange was sitting unused two tokens away. */
.weekly-hours-seg-day { background: var(--chart-1); flex-shrink: 0; }
.weekly-hours-seg-night { background: var(--chart-2); flex-shrink: 0; }
.weekly-hours-label { font-size: 0.66rem; color: var(--muted); white-space: nowrap; }
.weekly-hours-total { font-size: 0.68rem; color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; min-height: 1em; }

/* Compact tag for a per-patient list that can hold many entries in one
   cell (Care Profile's Services/Conditions) -- .status-badge's pill shape
   and padding are sized for "one status per row" elsewhere in the app;
   several of those stacked in a table cell (with an inline remove button
   on each) add up to a lot of vertical height. Smaller box, not a pill. */
.care-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--state-info-soft);
  color: var(--accent);
  padding: 2px 7px;
  border-radius: 6px;
  font-size: 0.72rem;
  font-weight: 600;
  white-space: nowrap;
  margin: 0 3px 3px 0;
  line-height: 1.5;
}

.admin-nav {
  display: flex;
  gap: 4px;
  overflow-x: auto;
  padding: 8px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-subtle);
}
.admin-nav a {
  padding: 8px 12px;
  border-radius: 8px;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}
.admin-nav a:hover { text-decoration: none; background: var(--surface-hover); }
.admin-nav a.active { color: var(--badge-ink); background: var(--accent); }

/* Document template field editor */
.doc-editor {
  display: flex;
  gap: 20px;
  align-items: flex-start;
}
.doc-palette {
  position: sticky;
  top: 16px;
  flex: 0 0 220px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-subtle);
  /* Smaller base size for the whole panel -- per-element sizes below are
     mostly already explicit (rem, not inherited), condensed 2026-08-25
     so more of the tool list is visible/scannable at once without
     scrolling (see the max-height/overflow note below for the related
     "have to scroll the whole page" fix from the same day). */
  font-size: 0.85rem;
  /* Sticky alone has no bound on its own height -- with enough palette
     groups (Who fills it in / Field type / Field Features / Selected
     Field) its content can be taller than the viewport, and with no
     overflow set here a scroll gesture over the panel has nothing to
     scroll *within* it, so it falls through to scrolling the whole page
     (found live 2026-08-25 -- "have to scroll down the entire document
     to scroll the tools panel"). Bounding height to the viewport (minus
     the sticky top offset on both sides) and scrolling internally is the
     standard fix for a sticky sidebar taller than the screen. */
  max-height: calc(100vh - 32px);
  overflow-y: auto;
}
.doc-palette-group { display: flex; flex-direction: column; gap: 4px; }
.doc-palette-label {
  font-size: 0.64rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.palette-role, .palette-field {
  display: block;
  width: 100%;
  text-align: left;
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  font-size: 0.76rem;
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
}
.palette-role + .palette-role { margin-top: 3px; }
.palette-field + .palette-field { margin-top: 3px; }
.palette-role.active { background: var(--accent); border-color: var(--accent); color: var(--badge-ink); }
.palette-field.active { background: var(--state-good-ink); border-color: var(--state-good-ink); color: var(--badge-ink); }
.doc-palette-hint { font-size: 0.68rem; color: var(--muted); line-height: 1.35; }

.doc-selected-panel {
  border-top: 2px solid var(--accent);
  padding-top: 10px;
  margin-top: 2px;
}
.doc-selected-panel label { font-size: 0.68rem; font-weight: 600; color: var(--muted); }
.doc-selected-panel input[type="text"],
.doc-selected-panel input[type="number"],
.doc-selected-panel select {
  width: 100%;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.78rem;
  font-family: inherit;
}
.doc-pos-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; margin-top: 4px; }
.doc-pos-grid input { padding: 4px; text-align: center; }
.doc-pos-grid-labels {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  font-size: 0.6rem;
  color: var(--muted);
  text-align: center;
  margin-top: 2px;
}

.doc-pages {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.page-canvas {
  position: relative;
  display: inline-block;
  max-width: 100%;
  border: 1px solid var(--border);
  border-radius: 4px;
  overflow: hidden;
}
.page-canvas img { display: block; max-width: 100%; height: auto; cursor: crosshair; }

.field-box {
  position: absolute;
  border: 2px solid;
  border-radius: 3px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
  padding: 1px 4px;
  overflow: hidden;
  cursor: move;
}
.field-box.selected {
  outline: 2px dashed var(--text);
  outline-offset: 2px;
  overflow: visible;
  z-index: 5;
}
.field-box.selected .field-box-label { overflow: visible; text-overflow: clip; }
.field-box-label {
  font-size: 0.62rem;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.field-box-delete {
  flex: 0 0 auto;
  border: none;
  background: rgba(0, 0, 0, 0.15);
  color: #fff;
  border-radius: 50%;
  width: 16px;
  height: 16px;
  line-height: 14px;
  font-size: 0.72rem;
  cursor: pointer;
  padding: 0;
}
.field-box-resize {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 9px;
  height: 9px;
  cursor: nwse-resize;
  background: rgba(0, 0, 0, 0.25);
  border-top-left-radius: 3px;
}
.field-role-admin { border-color: var(--accent); background: rgba(21, 101, 192, 0.14); color: var(--text); }
.field-role-recipient { border-color: var(--state-good); background: color-mix(in srgb, var(--state-good), transparent 86%); color: var(--state-good-ink); }
/* The recipient's own signature/date -- blue in both roles' overlay so it
   reads as "theirs, locked until hand-off" at a glance, overriding the
   green field-role-recipient default (higher specificity via the compound
   selector, wins regardless of source order). */
.field-box.recipient-locked-field { border-color: var(--field-locked); background: color-mix(in srgb, var(--field-locked), transparent 86%); color: var(--field-locked); }

.field-box-value {
  font-size: 0.7rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.field-box-empty { font-style: italic; opacity: 0.7; font-weight: 500; }
.field-box-input {
  width: 100%;
  height: 100%;
  border: none;
  background: transparent;
  font-size: 0.72rem;
  font-weight: 600;
  color: inherit;
  padding: 0 2px;
}
.field-box-input:focus { outline: 2px solid currentColor; background: rgba(255, 255, 255, 0.6); }

/* Signature pad -- shared by the Forms system (_status_bar.html) and the
   Document overlay fill pages (patient/provider document_fill.html), see
   app/static/signature_pad.js. The small .sig-pad-canvas is a preview
   only; tapping it opens the large popup below to actually draw in.
   Background deliberately stays literal white, not --surface: it's
   simulating ink on paper (matching the completed PDF the signature ends
   up on), so it should look the same regardless of which theme the rest
   of the app is in -- same reasoning as .invoice-sheet below. */
.sig-pad-canvas { border: 1px solid #ccc; border-radius: 4px; background: #fff; display: block; cursor: pointer; max-width: 100%; }
.sig-pad-canvas[data-disabled] { cursor: default; }
.sig-pad-canvas.has-signature { border-color: var(--accent); }
.sig-pad-actions { margin-top: 4px; }
/* Typed-by-default signature: the text input and the pad above are
   mutually exclusive (JS toggles which is display:none) -- sig-draw-toggle
   is the one-line link back to drawing when the text box is showing. */
.sig-input-wrap .sig-draw-toggle { margin-top: 4px; }
/* Icon-only variant for the Document system's small, absolutely-positioned
   field boxes (no room for a full-width text button there) -- corner
   overlay, doesn't affect the box's own layout. */
.sig-input-wrap { position: relative; }
.sig-draw-toggle-icon {
  position: absolute; top: -2px; right: -2px; width: 20px; height: 20px;
  padding: 0; line-height: 1; font-size: 12px; border-radius: 50%;
}

/* The recipient's own signature/date on the admin-assisted page -- blue
   either way: locked+disabled during bulk-fill, live+editable once handed
   off. Distinguishes "this part is theirs" at a glance from everything
   else on the page, which the admin fills directly. */
.recipient-locked-field { border-left: 3px solid var(--field-locked); padding-left: 10px; background: color-mix(in srgb, var(--field-locked), transparent 95%); border-radius: 4px; }
.recipient-locked-field label { color: var(--field-locked); }
.recipient-locked-field input, .recipient-locked-field .sig-pad-canvas { border-color: var(--field-locked); }
.recipient-locked-note { color: var(--field-locked); font-style: italic; }

/* Small, unobtrusive per-viewer debug id -- see app/debug_ref.py. Shown on
   every form/document page (patient/provider self-service and admin) so a
   reported issue can be correlated to a specific patient/provider/admin
   without reading out a name or an internal DB id. */
.debug-ref { font-size: 0.68rem; color: var(--muted); font-family: monospace; margin: 4px 0 8px; user-select: all; }

.sig-popup-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 32, 0.55);
  align-items: center;
  justify-content: center;
  padding: 16px;
  z-index: 1001;
}
.sig-popup-backdrop.open { display: flex; }
.sig-popup-panel {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 14px;
  width: 100%;
  max-width: 560px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sig-popup-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 700;
  font-size: 0.95rem;
}
.sig-popup-close { background: none; border: none; font-size: 1.4rem; line-height: 1; cursor: pointer; color: var(--muted); padding: 0 4px; }
.sig-popup-canvas {
  width: 100%;
  /* Tall enough to draw a real signature comfortably with a finger on a
     phone/tablet -- the whole point of the popup over the tiny inline
     preview -- but capped so it still fits a short landscape screen. */
  height: min(45vh, 320px);
  border: 1px solid #ccc;
  border-radius: 6px;
  /* Literal white, not --surface -- same paper-simulation reasoning as
     .sig-pad-canvas above. */
  background: #fff;
  touch-action: none;
  display: block;
}
.sig-popup-actions { display: flex; justify-content: flex-end; gap: 8px; }
.sig-popup-done { background: var(--accent); color: var(--badge-ink); border: none; border-radius: 8px; padding: 8px 16px; font-weight: 600; cursor: pointer; }

@media (max-width: 720px) {
  .doc-editor { flex-direction: column; }
  /* Stacked above the document here, not pinned beside it -- the
     viewport-height cap only exists to bound the sticky desktop sidebar,
     so drop it back to natural height and let the page itself scroll,
     same as everything else on a mobile layout. */
  .doc-palette { position: static; flex: 1 1 auto; width: 100%; max-height: none; overflow-y: visible; }
}

/* Message threads */
.message-thread {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 16px;
  max-height: 60vh;
  overflow-y: auto;
  padding: 4px;
}
.message-bubble {
  align-self: flex-start;
  max-width: 75%;
  background: var(--surface-hover);
  border-radius: 12px;
  padding: 8px 12px;
}
.message-bubble.mine {
  align-self: flex-end;
  background: var(--bg-subtle);
}
.message-meta { font-size: 0.7rem; color: var(--muted); margin-bottom: 3px; }
.message-body { font-size: 0.9rem; white-space: pre-wrap; word-break: break-word; }
/* Scheduling calendar -- Google-Calendar-style day/week time grid +
   month overview, sharing one visual language (clean 1px borders,
   generous cell size, a red-accent "today" marker). */
.calendar-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 18px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border);
}
.calendar-nav-left { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
/* What you DO to the calendar, kept together at the right end: the view
   picker, the two add buttons and Print. Spread along the row with the key
   between them, they did not read as a group. */
.calendar-nav-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-left: auto;
}
.calendar-today-btn { text-decoration: none; }
.calendar-nav-arrows { display: flex; gap: 2px; }
.calendar-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--text);
}
.calendar-arrow:hover { background: var(--surface-hover); text-decoration: none; }
.calendar-label { font-size: 1.2rem; font-weight: 700; }
.calendar-view-select {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 32px 8px 14px;
  font-size: 0.88rem;
  font-weight: 600;
  background: var(--surface);
  cursor: pointer;
}
.calendar-print-heading { display: none; }

@media print {
  .topbar, .admin-nav, .top-actions, .calendar-nav, .flash,
  .calendar-modal-backdrop, .visit-popover-panel, .no-print {
    display: none !important;
  }
  .calendar-print-heading {
    display: block;
    font-size: 1.1rem;
    font-weight: 700;
    margin-bottom: 14px;
  }
  .calendar-frame { border-color: #999; }
  table.calendar-month, .calendar-timegrid-wrap { break-inside: avoid; }
  table.calendar-month tr { break-inside: avoid; }
  .calendar-visit-block, .cal-chip, .visit-card {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
    break-inside: avoid;
  }
  .invoice-totals { break-inside: avoid; }
  .invoice-sheet table.admin-table tr { break-inside: avoid; }
  .invoice-sheet {
    box-shadow: none;
    border: none;
    padding: 0;
  }
  .invoice-accent-bar { background: #333 !important; }
  .invoice-status { border-color: #333 !important; color: #333 !important; }
}

/* A printed invoice stays literal white on purpose -- it represents a real
   document a patient or the agency prints/downloads, same reasoning as
   .sig-pad-canvas above, not an oversight in the dark-mode sweep. */
.invoice-sheet {
  /* The sheet is white in both themes (see the note above), so its contents
     cannot inherit the page's theme tokens -- in dark mode --text is
     near-white and the whole invoice would disappear into the paper.
     Re-declaring the light values here re-points every descendant at the
     surface it is really on; no rule inside the sheet needs to know. */
  --text: #1f2933;
  --muted: #66717e;
  --accent: #1565c0;
  --border: #d8dee5;
  --surface: #fff;
  --bg-subtle: #eaf6fb;
  /* Zebra rows and inset panels. Missed when this block was written, so every
     even row of an invoice took the DARK #1b2129 against white paper --
     dark-on-dark and unreadable. Only on screen: print forces the light theme,
     so the printed copy came out fine and hid it. */
  --surface-subtle: #fafbfc;
  --state-good: #2e7d32; --state-good-soft: #e6f4ea; --state-good-ink: #226b3a;
  --state-warn: #c99700; --state-warn-soft: #fff3c4; --state-warn-ink: #6b4e00; --state-missed: #c2570b; --state-missed-soft: #fdeee2; --state-missed-ink: #8a3d06;
  --state-neutral: #9aa5ad; --state-neutral-soft: #eceef0; --state-neutral-ink: #5b6470;
  color: var(--text);
  max-width: 900px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 32px 36px 28px;
  overflow: hidden;
}
.invoice-accent-bar {
  height: 6px;
  margin: -32px -36px 26px;
  background: linear-gradient(90deg, var(--accent), #80a156);
}
.invoice-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 24px;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--border);
}
.invoice-brand { display: flex; gap: 14px; align-items: flex-start; font-size: 0.85rem; color: var(--muted); line-height: 1.5; }
.invoice-brand-line { color: var(--muted); }
.invoice-agency-name { font-size: 1.2rem; font-weight: 800; color: var(--text); margin-bottom: 3px; letter-spacing: -0.01em; }
.invoice-meta { text-align: right; font-size: 0.9rem; color: var(--muted); min-width: 230px; }
.invoice-title { font-size: 1.6rem; font-weight: 800; letter-spacing: 0.06em; color: var(--accent); margin-bottom: 8px; }
.invoice-meta-row { display: flex; justify-content: flex-end; gap: 8px; padding: 1px 0; }
.invoice-meta-row strong { color: var(--text); }
.invoice-number-value { font-family: "SFMono-Regular", Consolas, monospace; font-size: 0.76rem; letter-spacing: -0.01em; }
.invoice-status {
  display: inline-block;
  margin-top: 10px;
  padding: 4px 14px;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border: 1px solid transparent;
}
.invoice-status-paid { background: var(--state-good-soft); color: var(--state-good-ink); border-color: var(--state-good); }
.invoice-status-sent { background: var(--bg-subtle); color: var(--accent); border-color: color-mix(in srgb, var(--accent), var(--surface) 65%); }
.invoice-status-void { background: var(--state-neutral-soft); color: var(--muted); border-color: var(--border); }
.invoice-status-draft { background: var(--state-warn-soft); color: var(--state-warn-ink); border-color: var(--state-warn); }

.invoice-parties {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 26px;
  font-size: 0.92rem;
}
.invoice-party-right { text-align: right; }
.invoice-party-label { font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin-bottom: 4px; font-weight: 700; }
.invoice-party-name { font-weight: 700; font-size: 1rem; margin-bottom: 2px; }

.invoice-section-title {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  color: var(--muted);
  margin: 0 0 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.invoice-table thead th { background: var(--bg-subtle); font-size: 0.8rem; }
.invoice-table tbody tr:nth-child(even) { background: var(--surface-subtle); }
/* No hover highlight anywhere on an invoice. table.admin-table gives every
   row a --surface-hover background, which is right for a list whose rows you
   click and wrong for a document: on the sheet's white paper the dark value
   painted a black ribbon that followed the cursor. Nothing on an invoice is
   clickable, so the cue was never meaningful here.

   Two rules, not one: the second keeps a zebra row on its own stripe while
   hovered, so the banding does not flicker off as the cursor crosses it. */
.invoice-sheet table.admin-table tbody tr:hover { background: transparent; }
.invoice-sheet .invoice-table tbody tr:nth-child(even):hover { background: var(--surface-subtle); }
.invoice-num-col { text-align: right; white-space: nowrap; }
.invoice-table-detail td, .invoice-table-detail th { font-size: 0.85rem; }

/* An invoice's dates, as a two-column list inside one cell: what happened on
   the left, when on the right, oldest first. A grid rather than lines of
   "sent Sep 3" text, so the dates line up under each other down the column
   and the whole history reads at a glance instead of the most recent event
   alone. */
.date-history {
  display: grid;
  grid-template-columns: max-content max-content;
  gap: 1px 8px;
  margin: 0;
  font-size: 0.76rem;
  white-space: nowrap;
}
.date-history dt { color: var(--muted); }
.date-history dd { margin: 0; font-variant-numeric: tabular-nums; }

.invoice-totals-wrap { display: flex; justify-content: flex-end; }
.invoice-totals { margin: 14px 0 0; min-width: 300px; max-width: 340px; background: var(--bg-subtle); border-radius: var(--radius); padding: 14px 18px; }
.invoice-totals-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 0.92rem; }
.invoice-totals-grand { border-top: 1px solid color-mix(in srgb, var(--accent), var(--surface) 65%); margin-top: 6px; padding-top: 10px; font-size: 1.15rem; font-weight: 800; color: var(--accent); }

.invoice-footer {
  margin-top: 32px;
  padding-top: 18px;
  border-top: 1px solid var(--border);
  text-align: center;
  font-weight: 600;
}
.invoice-footer .help-text { font-weight: 400; margin-top: 4px; }

.calendar-frame {
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

/* Month view */
table.calendar-month { width: 100%; border-collapse: collapse; table-layout: fixed; }
table.calendar-month th {
  text-align: center;
  padding: 10px 4px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom: 1px solid var(--border);
  background: var(--surface-subtle);
}
table.calendar-month td {
  vertical-align: top;
  border: 1px solid var(--border);
  padding: 6px;
  height: 130px;
  width: 14.28%;
  overflow: hidden;
}
table.calendar-month td.muted { background: var(--surface-subtle); }
table.calendar-month td.muted .calendar-day-number { color: var(--muted); }
/* Density tint -- how many visits land on the day, at a glance instead of
   only after counting chips. Steps through the sequential blue scale
   (--seq-*, the same one calendar density was always meant to use, see
   its :root comment); a plain "0 visits" cell gets no tint at all. */
table.calendar-month td.cal-density-1 { background: var(--seq-100); }
table.calendar-month td.cal-density-2 { background: var(--seq-250); }
table.calendar-month td.cal-density-3 { background: var(--seq-400); }
table.calendar-month td.cal-density-4 { background: var(--seq-550); }
/* Text needs to flip to stay legible once the tint gets dark enough to
   compete with it -- --seq-400 and up are dark blues in both themes. */
table.calendar-month td.cal-density-3, table.calendar-month td.cal-density-4 { color: #fff; }
table.calendar-month td.cal-density-3 .calendar-day-number,
table.calendar-month td.cal-density-4 .calendar-day-number { color: #fff; }
/* Unstaffed is a different question from "how many" -- an inset red
   ring, not a fill, so it layers over any density tint without fighting
   it for the same visual channel. */
table.calendar-month td.cal-day-unstaffed { box-shadow: inset 0 0 0 2px var(--state-critical); }
.calendar-day-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  font-size: 0.85rem;
  font-weight: 700;
  margin-bottom: 4px;
  color: var(--text);
}
.calendar-day-number:hover { background: var(--surface-hover); text-decoration: none; }
/* Today is blue, not red. Red is the app's alarm colour and today is not an
   alarm -- it was competing with the states that genuinely need attention. */
.calendar-day-number.today { background: var(--accent); color: var(--badge-ink); }
.calendar-day-number.today:hover { background: var(--accent-dark); }
.calendar-day-chips { display: flex; flex-direction: column; gap: 2px; }
.cal-chip {
  display: block;
  width: 100%;
  font-family: inherit;
  font-size: 0.68rem;
  font-weight: 600;
  background: rgba(21, 101, 192, 0.12);
  /* A token, not a fixed navy. Dark mode redefines --text; the hardcoded
     #0d3f75 stayed navy on a near-black ground, which is what made the month
     grid unreadable there. */
  color: var(--text);
  border: none;
  border-left: 3px solid var(--accent);
  border-radius: 3px;
  padding: 2px 5px;
  margin-bottom: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: left;
  cursor: pointer;
}
button.cal-chip:hover { filter: brightness(0.95); }
a.cal-more { display: block; }
.cal-more { font-size: 0.64rem; color: var(--muted); padding-left: 5px; }
.calendar-day-count {
  font-family: Georgia, "Times New Roman", serif;
  font-style: italic;
  font-size: 0.72rem;
  color: var(--accent);
  white-space: nowrap;
}
.calendar-add-btn {
  min-height: 0;
  width: 16px;
  height: 16px;
  padding: 0;
  line-height: 1;
  font-size: 0.65rem;
  font-weight: 700;
  border-radius: 50%;
  background: var(--surface);
  color: var(--muted);
  border: 1px solid var(--border);
  flex-shrink: 0;
}
.calendar-add-btn:hover { background: var(--accent); color: var(--badge-ink); border-color: var(--accent); }

/* Day/week time grid */
.calendar-timegrid-wrap { display: flex; flex-direction: column; }
/* The day grid scrolls in its own box instead of making the page 1400px
   long. 68vh keeps the nav, the all-day strip and the event list either side
   of it on screen, which is what makes the day view usable as a day view
   rather than a very tall picture. */
.calendar-timegrid-scroll { overflow-y: auto; max-height: 68vh; overscroll-behavior: contain; }
/* Room for the 12 AM label. Every hour label is pulled up half its own
   height to sit ON its line, so the midnight one -- the only label at top:0
   -- had its upper half outside the grid and read as a clipped "12 AM". */
.calendar-timegrid-body { padding-top: 9px; }
.calendar-hour-gutter { flex: 0 0 56px; }
.calendar-timegrid-header {
  display: flex;
  border-bottom: 1px solid var(--border);
  background: var(--surface-subtle);
}
.calendar-col-header {
  flex: 1;
  text-align: center;
  padding: 10px 4px;
  border-left: 1px solid var(--border);
  color: var(--text);
}
.calendar-col-header:hover { background: var(--surface-hover); text-decoration: none; }
.calendar-col-weekday { display: block; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }
.calendar-col-daynum {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font-size: 1rem;
  font-weight: 700;
  margin-top: 2px;
}
.calendar-col-daynum.today { background: var(--accent); color: var(--badge-ink); }
.calendar-untimed-row { display: flex; border-bottom: 1px solid var(--border); min-height: 28px; }
.calendar-untimed-label { font-size: 0.65rem; color: var(--muted); padding: 4px 6px 0 0; text-align: right; }
.calendar-untimed-col { flex: 1; display: flex; flex-wrap: wrap; gap: 3px; padding: 4px 6px; border-left: 1px solid var(--border); }
.calendar-timegrid-body { position: relative; display: flex; }
.calendar-hour-labels { position: relative; }
.calendar-hour-label {
  position: absolute;
  right: 8px;
  transform: translateY(-50%);
  font-size: 0.68rem;
  color: var(--muted);
  white-space: nowrap;
}
.calendar-timegrid-col {
  position: relative;
  flex: 1;
  border-left: 1px solid var(--border);
}
.calendar-hour-line { position: absolute; left: 0; right: 0; border-top: 1px solid var(--border); }
/* Night is shaded and the working day is not, so the eye lands on the part
   of a 24-hour column anything is likely to be in. A tint rather than a
   hidden range: overnight and live-in shifts are real visits and have to
   stay both visible and droppable. */
.calendar-night-band { position: absolute; left: 0; right: 0; background: var(--surface-subtle); opacity: 0.55; pointer-events: none; }
.calendar-timegrid-col.today { background: rgba(198, 40, 40, 0.025); }
/* A day-view block is the full width of the column, so it can afford the
   time on its own line above the name -- and a floor under its height, since
   a 15-minute visit at 56px/hour is 14px and can hold neither. */
.calendar-timegrid-col .calendar-visit-block { min-height: 20px; }
.calendar-timegrid-col .calendar-visit-time { font-size: 0.68rem; opacity: 0.85; }
.calendar-now-line { position: absolute; left: 0; right: 0; border-top: 2px solid var(--danger); z-index: 3; pointer-events: none; }
.calendar-now-dot {
  position: absolute;
  left: -5px;
  top: -5px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--danger);
}
.calendar-visit-block {
  position: absolute;
  background: rgba(21, 101, 192, 0.14);
  border: none;
  border-left: 3px solid var(--accent);
  border-radius: 4px;
  padding: 3px 6px;
  overflow: hidden;
  font-size: 0.7rem;
  font-family: inherit;
  line-height: 1.25;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.08);
  cursor: pointer;
  text-align: left;
}
.calendar-visit-block:hover { box-shadow: 0 2px 6px rgba(0,0,0,0.18); z-index: 4; }
/* Distinguishes a real scheduled visit from a company event on the main
   admin calendar, which overlays both in the same hour grid -- green
   instead of the default blue so the two are visually distinct at a glance. */
.calendar-visit-block.real-visit { background: rgba(46, 125, 50, 0.14); border-left-color: #2e7d32; }
/* ...and the same four states on the timed blocks the week and day views draw.
   Written with both classes so they beat .real-visit's green regardless of
   source order -- that green is the "this is a real visit, not an event"
   colour, which stops meaning anything once the visit's own state has one. */
.calendar-visit-block.real-visit.cal-done      { background: color-mix(in srgb, var(--state-good) 14%, transparent);     border-left-color: var(--state-good); }
.calendar-visit-block.real-visit.cal-missed    { background: color-mix(in srgb, var(--state-missed) 14%, transparent); border-left-color: var(--state-missed); }
.calendar-visit-block.real-visit.cal-requested { background: color-mix(in srgb, var(--state-warn) 16%, transparent);     border-left-color: var(--state-warn); }
.calendar-visit-block.real-visit.cal-future    { background: color-mix(in srgb, var(--state-neutral) 16%, transparent);  border-left-color: var(--state-neutral); }
/* Upcoming with nobody on it. The same red as a cancellation, because that
   is what it turns into if nothing is done -- and a dashed edge rather than
   a solid one, so the two reds are still tellable apart: a cancellation is
   settled, this one is a hole somebody still has to fill. */
/* Nobody assigned. A dashed edge alone was invisible in the week view, where
   a block is a thin horizontal bar among thirty others -- so it is hatched
   as well: diagonal stripes read as "not filled in" at any size, and no
   other state on this calendar has a texture. The red says what kind of
   problem it is; the stripes are what make it findable. */
.calendar-visit-block.real-visit.cal-unstaffed {
  background-color: color-mix(in srgb, var(--state-critical) 16%, transparent);
  background-image: repeating-linear-gradient(
    135deg,
    color-mix(in srgb, var(--state-critical) 30%, transparent) 0 6px,
    transparent 6px 12px);
  border-left: 3px dashed var(--state-critical);
}
/* Timed blocks in the week and day grids read DOWN the column.
   A day column is roughly 150px wide and a two-hour visit is ~90px tall, so
   horizontal text got four or five characters and an ellipsis -- the patient's
   name, which is the only reason to look at the block, was the part cut. Turned
   ninety degrees it has the block's whole height to run in.

   The name is full-strength text, not muted: at this size a grey on a tinted
   ground is the thing people said they could not read. */
/* These blocks are <button>s, and the global button rule paints text #fff for
   solid-background buttons. They have a pale tinted ground, so any text that
   set no colour of its own was white on near-white and simply invisible.
   .cal-chip already sets its own colour for exactly this reason. Applies to
   every calendar -- the bug was never specific to one. */
.calendar-visit-block { color: var(--text); }

/* Turned ninety degrees ONLY on the agency calendar. A day there carries
   several visits across seven columns, so a block is ~150px wide and
   horizontal text got a few characters and an ellipsis -- cutting the
   patient's name, which is the only reason to look at it. A patient's or
   caregiver's own calendar has a visit or two a day and plenty of width, so
   rotating text there would make it harder to read for no gain. */
/* The DAY view's blocks. One day across the whole screen means a wide block,
   so the two names people actually read -- patient and caregiver -- run across
   it, centred, at a size you can take in from a step back. They used to be
   rotated up the side at 0.6rem, which is the right answer for the WEEK grid's
   ~150px columns and the wrong one here. */
.calendar-visit-block-agency {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 4px 10px;
}
.calendar-visit-block-agency .calendar-visit-who {
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.25;
  white-space: normal;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
/* A company event is not a visit. Same blue as the key's swatch, with a
   border of its own, so a block that is nobody's appointment cannot be read
   as somebody's. */
/* A short company event: title and time on ONE line, because the block is
   only as tall as its duration and a 30-minute event cannot show two. Ellipsis
   rather than wrap for the same reason -- a wrapped second line would be
   clipped, which is the bug this replaces. */
.calendar-event-oneline {
  display: flex;
  align-items: baseline;
  gap: 6px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.calendar-event-oneline .calendar-visit-time { flex: none; opacity: 0.75; }

.calendar-event-block {
  border-left: 4px solid rgba(91, 155, 240, 0.9) !important;
  background: rgba(91, 155, 240, 0.18) !important;
}
/* One step down before the label is dropped entirely -- a slightly smaller
   name still reads, and buys roughly a fifth more characters in the same
   height. */
.calendar-visit-who-tight { font-size: 0.6rem; letter-spacing: -0.01em; }
.calendar-visit-time { font-weight: 700; }
.calendar-visit-who { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.calendar-visit-notes { color: var(--muted); font-size: 0.65rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Visit detail popover (opened from month/week chips) */
.calendar-modal-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 60;
}
.calendar-modal-backdrop.open { display: block; }
.visit-popover-panel {
  display: none;
  position: fixed;
  /* Anchored to a fixed offset from the top, not vertically centered via
     top:50%/transform -- centering recalculates against the live viewport
     height, which iOS shrinks the instant an <input> inside the panel is
     focused (on-screen keyboard). That shift mid-tap could land the touch
     on the backdrop instead of the field, closing the modal before it
     could focus (found on a real device, 2026-08-25). A top-anchored
     position doesn't move when the keyboard opens, since it isn't a
     function of viewport height. */
  top: 6vh;
  left: 50%;
  /* translateZ(0) forces this onto its own GPU-composited layer -- iOS
     Safari has a known bug where a position:fixed element toggled from
     display:none to display:block via a JS class change (exactly what
     openRowModal does) doesn't always get promoted/re-composited above a
     sibling fixed element in the correct order on the first paint, even
     with a higher z-index. The visible symptom is the backdrop's 45%-black
     tint appearing to render OVER the panel instead of under it -- a
     solid white panel looking grey/washed out (reported live, 2026-08-25,
     confirmed not a device color-filter setting). background-color is
     also pinned explicitly (not just background) since that's the
     property compositing actually keys off. */
  transform: translateX(-50%) translateZ(0);
  z-index: 61;
  background-color: var(--surface);
  border-radius: 12px;
  padding: 20px;
  max-width: 380px;
  width: 90%;
  max-height: 85vh;
  overflow-y: auto;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
}
.visit-popover-panel.open { display: block; }
.visit-popover-panel .visit-card { border: none; padding: 0; box-shadow: none; }
.visit-popover-close {
  float: right;
  border: none;
  background: none;
  font-size: 1.3rem;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
  padding: 2px 4px;
}
.visit-popover-close:hover { color: var(--text); }

@media (max-width: 640px) {
  table.calendar-month td { height: 72px; padding: 3px; }
  .calendar-day-chips { display: none; }
  .cal-chip { font-size: 0.6rem; }
  .calendar-label { font-size: 1rem; }
  .calendar-hour-gutter { flex-basis: 42px; }
  .calendar-hour-label { font-size: 0.6rem; right: 4px; }
  .calendar-col-header { padding: 6px 2px; }
}

/* Chats floating widget -- ported from fshc-nurse-portal, adapted to an
   in-panel thread view (list <-> thread, back button, same panel) instead
   of nurse-portal's separate Messenger-style popup window -- meaningfully
   less JS/state for a smaller-scale app, same "chat panel" UX family.
   Deliberately NOT a full-page backdrop like .calendar-modal-backdrop: the
   rest of the page/nav stays visible and clickable behind it. */
.nav-badge {
  display: inline-block;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--badge-ink);
  font-size: 0.65rem;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  vertical-align: middle;
  margin-left: 2px;
}
.nav-badge.mentioned-badge { background: var(--category-tagged); }
/* The Chats trigger's own unread count, next to the mentioned one above --
   green for the same reason the dot inside the widget is. */
.nav-badge.chats-unread-badge { background: var(--category-included); }
.chat-widget-trigger {
  border: none;
  background: none;
  cursor: pointer;
  font-size: 1.2rem;
  line-height: 1;
  padding: 2px 4px;
  position: relative;
  color: var(--text);
}
.chat-widget-trigger:hover { color: var(--accent); }
.chat-widget-panel {
  display: none;
  position: fixed;
  bottom: calc(16px + env(safe-area-inset-bottom));
  right: calc(16px + env(safe-area-inset-right));
  width: 340px;
  max-width: calc(100vw - 32px);
  height: 480px;
  /* 100vh on iOS is the LARGE viewport -- it ignores the browser/PWA chrome,
     so a panel sized against it runs taller than what is actually on screen
     and the last conversation in the list is cut off below the fold. 100dvh
     is the live visible height. The vh line stays first as the fallback for
     browsers without dvh; the dvh line wins wherever it is understood. */
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  z-index: 900;
  flex-direction: column;
  overflow: hidden;
}
.chat-widget-panel.open { display: flex; }
.chat-widget-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  background: var(--accent);
  color: var(--badge-ink);
  flex-shrink: 0;
}
.chat-widget-header h4 { margin: 0; font-size: 0.95rem; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-widget-header button {
  border: none;
  background: none;
  color: var(--badge-ink);
  cursor: pointer;
  font-size: 1rem;
  padding: 2px 6px;
  line-height: 1;
}
.chat-widget-back { border: none; background: none; color: var(--badge-ink); cursor: pointer; font-size: 0.9rem; padding: 2px 4px; }
.chat-widget-body { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 8px 8px calc(8px + env(safe-area-inset-bottom)); display: flex; flex-direction: column; }
.chat-widget-thread-row {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  cursor: pointer;
  padding: 8px;
  border-radius: 8px;
  border-bottom: 1px solid var(--border);
  font: inherit;
  color: inherit;
}
.chat-widget-thread-row:hover { background: var(--bg-subtle); }
.chat-widget-thread-row.mentioned { background: var(--mention-bg); }
.chat-widget-thread-subject { font-weight: 600; font-size: 0.85rem; display: flex; align-items: center; gap: 6px; }
.chat-widget-thread-preview { color: var(--muted); font-size: 0.78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
/* Same two colours as the Messages tags and nav counts: an unread chat you
   are simply in is "included" green, one that @-tagged you is "tagged"
   orange. It was accent blue and green, which matched neither. */
.chat-widget-unread-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--category-included); display: inline-block; flex-shrink: 0; }
.chat-widget-unread-dot.mentioned { background: var(--category-tagged); }
.chat-widget-thread-row-wrap { display: flex; align-items: stretch; gap: 2px; }
.chat-widget-thread-row-wrap .chat-widget-thread-row { flex: 1; min-width: 0; }
.chat-widget-thread-delete {
  flex-shrink: 0;
  align-self: center;
  border: none;
  background: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  padding: 4px 8px;
  border-radius: 6px;
}
.chat-widget-thread-delete:hover { color: var(--danger); background: var(--bg-subtle); }
.chat-widget-load-more { display: block; width: calc(100% - 16px); margin: 8px; }
.chat-widget-empty { color: var(--muted); font-size: 0.85rem; text-align: center; padding: 24px 8px; }
.chat-widget-thread-messages { flex: 1; display: flex; flex-direction: column; gap: 10px; overflow-y: auto; padding: 4px; }
.chat-widget-add-people { flex-shrink: 0; border-top: 1px solid var(--border); padding: 8px; max-height: 180px; overflow-y: auto; display: none; }
.chat-widget-footer { flex-shrink: 0; border-top: 1px solid var(--border); padding: 8px; display: flex; gap: 6px; align-items: center; }
.chat-widget-footer input { flex: 1; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 0.9rem; }
.chat-widget-add-btn { flex-shrink: 0; }
.message-bubble.mentioned { background: var(--mention-bg); border: 1px solid var(--mention); }
.message-bubble.mentioned.mine { background: var(--mention-bg); }
.message-attachment { margin-top: 6px; }
.message-attachment-thumb { max-width: 200px; max-height: 160px; border-radius: 8px; border: 1px solid var(--border); display: block; }
.message-attachment-file { display: inline-flex; align-items: center; gap: 4px; font-size: 0.85rem; background: rgba(0, 0, 0, 0.04); border-radius: 6px; padding: 4px 8px; }
.message-delete { border: none; background: none; color: var(--danger); font-size: 0.7rem; cursor: pointer; padding: 0; text-decoration: underline; }
.people-picker-quickselect { width: 100%; margin-top: 6px; padding: 7px 8px; border: 1px solid var(--border); border-radius: 8px; font-size: 0.85rem; box-sizing: border-box; color: var(--muted); }
/* /messages thread-list row highlight -- "ever mentioned" (mention-highlight-row)
   vs. unread-scoped everything-else (unread-other-row), same blue/green
   split as the nav badges and message bubbles above. */
.mention-highlight-row { background: var(--mention-bg) !important; }
.mention-filter-link { color: var(--mention); font-weight: 600; }
.mention-filter-link.active { text-decoration: underline; }
/* Message category filter chips (Messages list -- Patient/Tagged/Included/
   Audit) -- an outlined pill in the category's own color, filled solid
   (+ a checkmark) once active. The colors come from CATEGORY_COLORS in
   _messages_list.html so the chip, the row's category tag and the legend
   swatch are always the exact same color for a given category -- one
   source of truth, not three hand-matched hex values. */
.category-filter-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 20px;
  border: 1.5px solid;
  font-size: 0.82rem;
  font-weight: 600;
  white-space: nowrap;
}
.category-filter-link:hover { text-decoration: none; opacity: 0.85; }
/* Messages nav badge cluster -- one tiny numbered pill per category, in
   that category's own colour (set inline in base.html, matching
   CATEGORY_COLORS in _messages_list.html). Sits beside the Messages nav
   link; each pill hides itself when its count is zero, so a quiet mailbox
   shows nothing at all rather than a row of zeroes. */
.messages-badge-grid {
  display: inline-flex;
  gap: 1px;
  /* Superscript, not inline: these sit tight against the end of the word
     like a footnote marker, rather than floating at mid-cap-height with a
     gap that made them read as separate nav items. The scoped size override
     below only shrinks this cluster -- the chat widget's own badges, which
     sit alone on an icon, keep the larger size where they need it. */
  vertical-align: super;
  margin-left: 1px;
  line-height: 0;
}
.messages-badge-grid .nav-badge {
  min-width: 12px;
  height: 12px;
  padding: 0 3px;
  border-radius: 6px;
  font-size: 0.55rem;
  line-height: 12px;
  margin-left: 0;
}

/* Denser variant of .admin-table, for list pages where fitting more rows
   above the fold matters more than roominess. Ported from
   fshc-nurse-portal so the same table reads the same in both apps. */
table.admin-table.admin-table-compact th,
table.admin-table.admin-table-compact td { padding: 4px 8px; font-size: 0.82rem; }
table.admin-table.admin-table-compact td:last-child .btn-small { min-height: 24px; padding: 3px 7px; font-size: 0.74rem; }

/* Messages list rows are Gmail-style: read state alone drives the
   background (white + bold while unread, light grey once read). Category
   never tints the row -- a thread can carry several categories at once, and
   a background can only say one thing, so categories are the coloured tags
   beside the subject instead. */
/* Unread has to be POSITIVE, not the absence of the read tint.
   Read rows were sunken and unread rows were left at the default row
   background -- which works on a white page, where sunken is visibly grey,
   and fails in dark mode, where #10141a and #161b22 are all but the same
   colour. Unread was then bold text and nothing else, so the only reliable
   way to find one was the Unread filter.

   So the unread row carries a tint of its own plus a bar down its leading
   edge: two signals, neither of which depends on the row beside it being a
   different shade. */
/* A chat announcing something about itself -- "X was added to the chat".
   Centred and quiet: it is not a bubble because nobody said it, and it must
   not compete with what people actually wrote. */
.chat-system-line {
  margin: 8px auto;
  padding: 3px 10px;
  max-width: 90%;
  text-align: center;
  font-size: 0.72rem;
  color: var(--muted);
  background: var(--surface-sunken);
  border-radius: 10px;
}

.msg-unread-row { background: var(--state-info-soft); }
.msg-unread-row td { font-weight: 600; }
.msg-unread-row td:first-child { box-shadow: inset 3px 0 0 var(--state-info); }
.msg-read-row { background: var(--surface-sunken); }
.msg-read-row td { font-weight: 400; }
/* This list's actions column only ever holds 1-2 icon buttons, but the
   shared table.admin-table td:last-child rule (sized for pages with several
   text actions) forces it much wider, stealing room Subject/Participants
   could use -- shrink both the cell and its buttons just for this table. */
.messages-table td:last-child { min-width: 0; }
.messages-table .icon-btn { width: 24px; height: 24px; min-height: 24px; font-size: 0.82rem; }
.messages-table .table-actions { gap: 2px; }
.unread-other-row { background: var(--bg-subtle) !important; }
.provider-tag { font-size: 0.65rem; color: var(--muted); font-weight: 400; }

/* iMessage-style people search + multi-select, shared by "new chat" and
   "add people to this chat". */
.people-picker-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
.people-picker-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--bg-subtle);
  border-radius: 999px;
  padding: 3px 6px 3px 10px;
  font-size: 0.82rem;
}
.people-picker-chip button { border: none; background: none; cursor: pointer; font-size: 0.95rem; line-height: 1; color: var(--muted); padding: 2px; }
.people-picker-chip button:hover { color: var(--danger); }
.people-picker-input { width: 100%; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; font-size: 0.9rem; box-sizing: border-box; }

/* @mentions -- green everywhere, distinct from the normal unread accent
   color, so "someone tagged me" always reads differently from plain
   unread activity. */
.mention-name { color: var(--accent); font-weight: 600; text-decoration: underline; text-decoration-color: rgba(21, 101, 192, 0.4); text-underline-offset: 2px; }
.mention-suggestions { box-sizing: border-box; }
.mention-suggestion-item { box-sizing: border-box; }

@media (max-width: 640px) {
  .chat-widget-panel {
    left: calc(8px + env(safe-area-inset-left));
    right: calc(8px + env(safe-area-inset-right));
    bottom: calc(8px + env(safe-area-inset-bottom));
    width: auto;
    max-width: none;
    height: min(70vh, 520px);
  }
}

/* Ported from fshc-nurse-portal's style.css -- subtab nav within a page
   (e.g. Patients / Billing on /admin/patients). */
.subtab-row {
  /* Relative so a trailing settings gear can be pinned right without taking
     part in the centring -- margin-left:auto on it would eat the free space
     and shove the tabs off-centre. */
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
  margin: 0 0 12px;
  padding: 4px;
  background: var(--surface-subtle);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

/* Sections inside a tab (Billing's What's billable / Claims / Payments).
   Deliberately quieter than the tab row above it -- no card, smaller type --
   so the two rows read as a hierarchy rather than as six equal destinations. */
.subtab-row-inner {
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
  margin: -4px 0 12px;
  font-size: 0.85rem;
}
.subtab-link {
  padding: 6px 14px;
  border-radius: 20px;
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}
/* The class has only ever been worn by an <a>, so it carries no reset. The
   import page's settings gear is a <button> (it opens a popup rather than
   navigating), and without this it renders with the browser's default button
   chrome -- a grey bevelled box among the pills. */
button.subtab-link {
  background: none;
  border: none;
  box-shadow: none;
  font-family: inherit;
  line-height: inherit;
  cursor: pointer;
}
.subtab-link:hover { text-decoration: none; background: var(--surface-hover); color: var(--text); }
.subtab-link.active { background: var(--bg-subtle); color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }

/* Onboarding forms (app/forms/) -- static legal text + inline fields,
   rendered from a Jinja .html file per form (see app/templates/forms/). */
/* Density pass: these forms are long legal documents with fields
   interleaved, so every bit of vertical rhythm compounds over a full
   page. Tightened throughout -- paddings, heading margins and line
   height -- without going below comfortable reading size. */
/* A signed legal document reads as a physical paper form, same reasoning
   as .sig-pad-canvas/.invoice-sheet -- literal white on purpose. */
.legal-doc {
  /* White in both themes, exactly like .invoice-sheet above -- these are
     documents a person prints or signs, and the paper does not change colour
     with the reader's theme. Missed in the dark-mode sweep, though: the
     background was pinned and the ink was not, so every descendant kept
     inheriting the page's --text, which is near-white in dark mode. The
     headings on /apply were the visible symptom, and the same card carries
     all the client and employee onboarding forms. Re-declaring the light
     values re-points every descendant at the surface it is really on. */
  --text: #1f2933;
  --muted: #66717e;
  --accent: #1565c0;
  --border: #d8dee5;
  --surface: #fff;
  --bg-subtle: #eaf6fb;
  /* Same reason as .invoice-sheet's: any token that paints a surface inside
     white paper has to be the light value, or a striped row or inset panel
     comes out dark-on-dark the moment somebody adds one. */
  --surface-subtle: #fafbfc;
  color: var(--text);
  max-width: 820px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 18px 22px;
}
.legal-doc h1 { font-size: 1.2rem; margin: 0 0 2px; }
.legal-doc h2 { font-size: 0.98rem; margin: 14px 0 6px; padding-bottom: 3px; border-bottom: 1px solid var(--border); }
.legal-doc h3 { font-size: 0.9rem; margin: 10px 0 4px; }
.legal-doc p { line-height: 1.42; margin: 0 0 7px; font-size: 0.9rem; }
.legal-doc ul { margin: 0 0 7px; padding-left: 18px; line-height: 1.42; font-size: 0.9rem; }
.legal-doc .letterhead { color: var(--muted); font-size: 0.8rem; margin-bottom: 10px; }
.legal-doc .form-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.legal-doc .checkbox-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; margin-bottom: 7px; }
.legal-doc .checkbox-grid.single-col { grid-template-columns: 1fr; }
.form-field-row { margin-bottom: 7px; }
.form-field-row label:not(.checkbox-row) { display: block; font-size: 0.8rem; color: var(--muted); margin-bottom: 2px; }
.form-field-row input[type=text], .form-field-row input[type=date], .form-field-row input[type=time],
.form-field-row input[type=number], .form-field-row input[type=email], .form-field-row input[type=tel],
.form-field-row select, .form-field-row textarea {
  width: 100%;
  max-width: 420px;
  padding: 6px 9px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.9rem;
  font-family: inherit;
}
.form-field-row textarea { max-width: 100%; min-height: 52px; resize: vertical; }
.form-field-row input:disabled, .form-field-row select:disabled, .form-field-row textarea:disabled {
  background: var(--bg-subtle);
  color: var(--text);
  opacity: 1;
  -webkit-text-fill-color: var(--text);
}
.form-signature-block { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 6px; padding-top: 10px; border-top: 1px solid var(--border); }
.form-signature-block .form-field-row { flex: 1; min-width: 220px; }
.legal-doc-status { margin-bottom: 10px; }
/* The two-button form footer (forms/_macros.html's submit_row): "Save and
   continue" carries on through the queue, "Save and finish" stops. Wraps on
   narrow screens rather than squashing, since a recipient may well be
   signing on a phone. */
.form-submit-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 10px; }

/* A <button> that has to read as an inline text link -- used by the
   onboarding lists, where each document/form title opens the fill popup.
   The global `button` rule above gives every button 44px min-height, 13px
   padding, centered text and a shadow, which turned those lists into a
   column of tall centred blocks with a wrapped title floating in the middle
   of one. Everything that makes a button look like a button is reset here;
   what stays is the click target. */
.link-button {
  display: inline;
  background: none;
  border: none;
  padding: 0;
  min-height: 0;
  box-shadow: none;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.link-button:hover { text-decoration: underline; opacity: 1; }

/* Onboarding tab: three stacked lists per patient (documents, checklist,
   forms), so the row's height is set by whichever list is longest and every
   pixel of per-entry spacing multiplies. Tight line-height and a 2px gap
   instead of the default run-on paragraph spacing -- the separators between
   patients then land close enough together to scan the page. */
.onboarding-entry {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 2px;
  line-height: 1.25;
}
.onboarding-table td { vertical-align: top; }
.onboarding-table .status-badge { line-height: 1.2; }

@media print {
  .legal-doc { box-shadow: none; border: none; padding: 0; }
}

/* File upload with photo preview + remove (app/static/file_upload_preview.js)
   -- shown once a file is picked/photographed, in place of the plain
   input, so the user can confirm (or clear/retake) before actually
   submitting the form. One .file-upload-row per selected file (multiple=""
   inputs can have more than one), each with its own Remove. */
.file-upload-wrap { display: inline-flex; }
.file-upload-preview {
  display: none;
  flex-direction: column;
  gap: 4px;
}
.file-upload-preview.open { display: flex; }
.file-upload-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px 4px 4px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-subtle);
  max-width: 220px;
}
.file-upload-thumb {
  display: none;
  width: 32px;
  height: 32px;
  object-fit: cover;
  border-radius: 5px;
  flex-shrink: 0;
}
.file-upload-filename {
  font-size: 0.78rem;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.file-upload-remove { width: 22px; height: 22px; min-height: 22px; font-size: 0.85rem; flex-shrink: 0; margin-left: auto; }

/* ---------------------------------------------------------------------
   Admin Dashboard (/admin/dashboard -- app/templates/admin/dashboard.html).
   Ported wholesale from fshc-nurse-portal's Dashboard so the two apps'
   landing pages read as one product rather than two. Deliberately
   compact: the page's whole value is that a shift's worth of "what needs
   attention" fits above the fold, so the tiles and rows are tighter than
   the .form-section / .admin-table pattern the editable surfaces use.
   Same tokens throughout -- no new colours, the severity tints reuse the
   .status-badge palette.
   --------------------------------------------------------------------- */
.dash-stats {
  display: grid;
  /* 110px, not nurse-portal's 118px: this app's strip carries 10 tiles to
     its 9, and the extra 8px is what lets all ten sit on one row at a
     normal desktop width instead of orphaning the last one. */
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: 8px;
  margin-bottom: 14px;
}
.dash-stat {
  display: block;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  text-decoration: none;
  color: inherit;
  transition: box-shadow 0.15s var(--ease), border-color 0.15s var(--ease);
}
a.dash-stat:hover { box-shadow: var(--shadow-sm); border-color: var(--accent); text-decoration: none; }
.dash-stat-value { display: block; font-size: 1.45rem; font-weight: 650; line-height: 1.1; font-variant-numeric: tabular-nums; }
.dash-stat-label { display: block; font-size: 0.72rem; color: var(--muted); margin-top: 2px; }
/* --state-warn is the FILL/line value of the warn pair -- as text on a plain
   surface it measured 2.65:1. --state-warn-ink is the same pair's text
   value, which is what a caption like this wants in either theme. */
.dash-stat-delta { display: block; font-size: 0.68rem; color: var(--state-warn-ink); margin-top: 1px; font-variant-numeric: tabular-nums; }
/* A tile only turns red/amber when its number is non-zero -- a calm
   dashboard has to be visibly calm, or the colour stops meaning
   anything. The template picks the class; zero always stays neutral. */
.dash-stat.is-alert .dash-stat-value { color: var(--danger); }
.dash-stat.is-warn .dash-stat-value { color: var(--state-warn-ink); }

.dash-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 12px;
  align-items: start;
}

/* Patient/provider dashboards: one column for paperwork (Needs Your
   Attention, then Completed, stacked), one for visits (next visit, then
   timesheet) -- unlike .dash-grid's auto-flowing cards above, this is a
   deliberate two-panel split, so it's a fixed 2-up rather than auto-fit. */
.dash-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  align-items: start;
}
.dash-col { display: flex; flex-direction: column; gap: 12px; }
@media (max-width: 860px) {
  .dash-two-col { grid-template-columns: 1fr; }
}
.dash-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 10px 12px;
  box-shadow: var(--shadow-xs);
}
.dash-card.is-alert { border-color: #e4b4ae; }
.dash-card.is-warn { border-color: #e8cf9a; }
.dash-card-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 6px;
  font-size: 0.88rem;
  font-weight: 620;
}
.dash-card-head .dash-count {
  margin-left: auto;
  font-size: 0.72rem;
  font-weight: 600;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--bg-subtle);
  color: var(--text);
}
.dash-card.is-alert .dash-count { background: var(--state-critical-soft); color: var(--state-critical-ink); }
.dash-card.is-warn .dash-count { background: var(--state-warn-soft); color: var(--state-warn-ink); }
/* A grid, not a flex line, so the three parts of a row -- who/what it is,
   the detail, and the date or count on the right -- start at the same x on
   every row of a card. Packed left by flex, the middle value began wherever
   the first one happened to end, so a column of names of different lengths
   scattered the details across a ragged diagonal and the card read as a
   list of unrelated fragments rather than three columns.

   Rows carry either two children or three, always with .dash-spacer last;
   the spacer is pinned to the third track so a two-child row still puts its
   date under every other row's date. */
.dash-row {
  display: grid;
  /* All three tracks flexible. max-content on the last one let a long value
     -- the escalation card's list of flagged concerns -- claim the whole row
     and squeeze the name and date beside it to one character wide. */
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 0.9fr);
  align-items: baseline;
  gap: 6px;
  padding: 4px 0;
  border-top: 1px solid var(--border);
  font-size: 0.82rem;
}
.dash-row:first-of-type { border-top: none; }
/* Two words and a number do not need two lines. The Messages card's rows are
   a label and a count, and the label wrapping mid-phrase made a two-row card
   twice as tall for no more information. */
.dash-row-oneline > * { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-row .dash-meta { color: var(--muted); font-size: 0.76rem; }
/* Long descriptions and names wrap inside their own track instead of
   pushing the next one out of line. */
.dash-row > * { min-width: 0; overflow-wrap: anywhere; }
.dash-row .dash-spacer { grid-column: 3; justify-self: end; text-align: right; }
/* The additional-services card puts a "Mark billed" form in the right-hand
   track. display:contents would drop it out of the grid cell it belongs to,
   so it stays a block and just sheds the default form margin; the button sits
   under whatever note is above it rather than beside it, because the track is
   the narrowest of the three. */
.dash-inline-form { margin: 4px 0 0; }
/* A two-part row (name, then date/time) has nothing in the middle track, so
   the value on the right was squeezed into the last third of the card and
   broke "9:00 AM-5:00 PM" across lines. Given the middle track as well, it
   has room for the whole time on one line. */
.dash-row > :nth-child(2).dash-spacer { grid-column: 2 / -1; }
/* A clock time is one thing to read. It may sit under the date (that break
   is written into the template), but it must not break inside itself. */
.dash-row .dash-time { white-space: nowrap; }
/* Rows whose middle value is the substance -- a patient's request, with the
   date, time and their own note in it. The name gets a narrow first track
   and wraps onto two or three lines rather than holding a column open for
   the longest name in the agency, which is what left the request reading in
   a slot barely wider than the name beside it. */
.dash-row-request { grid-template-columns: minmax(0, 5.5rem) minmax(0, 1fr) max-content; }
/* A row whose third value is prose rather than a date: name and date keep
   the positions every other card uses, and the sentence gets a full-width
   line of its own underneath instead of a column sized for "09/05". */
.dash-row-stack { grid-template-columns: minmax(0, 1fr) minmax(0, auto); }
.dash-row-stack .dash-spacer { grid-column: 2; }
.dash-row-flags { grid-column: 1 / -1; color: var(--danger); }
.dash-more { font-size: 0.74rem; color: var(--muted); margin: 6px 0 0; }
.dash-empty { font-size: 0.8rem; color: var(--muted); padding: 4px 0; }

/* ==========================================================================
   Mobile / tablet hardening pass -- 2026-08-30
   Ported from fshc-nurse-portal's identical pass (same shared CSS lineage).
   Audited with a Playwright device matrix (iPhone 390, iPad portrait 768,
   iPad landscape 1024, desktop 1440) across the admin, provider and patient
   surfaces. Every rule below fixes a defect that pass actually observed.
   ========================================================================== */

/* 1. Stacked-card rows must never crush the value.
   Below 640px a .responsive-table td becomes a flex row of
   [::before label][value]. With no flex sizing a long label competes with
   the value for the row and both shrink, breaking text one character per
   line. Pin the label to its content width and let the value drop to its
   own full-width line when it can't sit beside the label. No cell in this
   app carries a desktop column cap today, but the layout is the same one
   nurse-portal shredded a diagnosis column in, so harden it here too. */
@media (max-width: 640px) {
  table.responsive-table td { flex-wrap: wrap; align-items: baseline; }
  table.responsive-table td::before { flex: 0 0 auto; }
  table.responsive-table td > * { min-width: 0; }
}

/* 2. Tablet: scroll wide admin tables, don't shred them.
   Between 641px and 1024px there was no breakpoint at all, so a wide admin
   table got the full desktop layout inside a 768px iPad and the columns
   compressed to a few characters each. These tables sit in a .table-scroll
   wrapper that already scrolls; a sensible min-width lets them use that
   scroll instead of compressing to illegibility. */
@media (min-width: 641px) and (max-width: 1024px) {
  .table-scroll { overflow-x: auto; }
  /* width:max-content sizes the table to what its content actually needs
     so no column is squeezed to the point that text breaks mid-word;
     min-width:100% keeps a narrow table filling the page rather than
     shrinking to its text. See the nurse-portal pass for the measurements
     that ruled out a fixed min-width. */
  .table-scroll table.admin-table { width: max-content; min-width: 100%; }
  .table-scroll {
    background:
      linear-gradient(to right, var(--surface) 30%, color-mix(in srgb, var(--surface), transparent 100%)) left center / 24px 100% no-repeat,
      linear-gradient(to left, var(--surface) 30%, color-mix(in srgb, var(--surface), transparent 100%)) right center / 24px 100% no-repeat,
      radial-gradient(farthest-side at 0 50%, rgba(15,23,32,0.14), transparent) left center / 10px 100% no-repeat,
      radial-gradient(farthest-side at 100% 50%, rgba(15,23,32,0.14), transparent) right center / 10px 100% no-repeat;
    background-attachment: local, local, scroll, scroll;
  }
}

/* 3. Touch targets.
   Scoped to pointer:coarse so desktop keeps its deliberately condensed
   density -- this only grows hit areas on an actual touch screen. WCAG 2.2
   AA (2.5.8) sets a 24x24px floor; Apple HIG asks 44x44pt. The provider and
   patient nav pills measured 28px tall -- these are the two roles most
   likely to be used one-handed on a phone in the field. */
@media (pointer: coarse) {
  .admin-nav a,
  .subtab-link,
  .topbar-link {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  table.admin-table td a,
  table.responsive-table td a,
  .dash-row a,
  /* Standalone "<- Back" links sit alone in .top-actions -- they're nav
     controls, not prose, so the WCAG 2.5.8 inline-text exception doesn't
     cover them and they need the 24px floor like any other control. */
  .top-actions > a { display: inline-block; min-height: 24px; }
  /* Bare <select>s outside .search-bar inherit the browser's default 19px
     height, under the WCAG 24px floor. .search-bar selects set their own
     34px height and are untouched. */
  select { min-height: 32px; }
}

/* 4. Horizontal nav is scrollable but gave no sign of it -- the last tab was
   sliced off at the screen edge with no cue. Edge-shadow affordance. */
.admin-nav {
  background:
    linear-gradient(to right, var(--surface-subtle) 30%, color-mix(in srgb, var(--surface-subtle), transparent 100%)) left center / 24px 100% no-repeat,
    linear-gradient(to left, var(--surface-subtle) 30%, color-mix(in srgb, var(--surface-subtle), transparent 100%)) right center / 24px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%, rgba(15,23,32,0.12), transparent) left center / 10px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(15,23,32,0.12), transparent) right center / 10px 100% no-repeat,
    var(--surface-subtle);
  background-attachment: local, local, scroll, scroll, scroll;
  scrollbar-width: none;
}
.admin-nav::-webkit-scrollbar { display: none; }

/* 5. Respect the notch / home indicator when the PWA is installed
   full-screen on a phone -- the caregiver clock-in flow is the one screen
   here most likely to be run as an installed app in the field. */
@media (max-width: 640px) {
  .topbar { padding-left: calc(16px + env(safe-area-inset-left));
            padding-right: calc(16px + env(safe-area-inset-right)); }
  body { padding-bottom: env(safe-area-inset-bottom); }
}

/* 7. Sortable column headers are links inside <th>; the td-only rule above
   missed them, leaving them 15px tall -- under the WCAG 24px floor and the
   hardest thing on the page to hit, since sorting is exactly what you reach
   for on a small screen. */
@media (pointer: coarse) {
  table.admin-table thead th a { display: inline-block; min-height: 24px; }
}

/* 8. Week/day calendar on a phone.
   The timegrid splits into one flex column per day. On a 390px screen that
   left each column ~19px wide, so a visit block was a 19px-wide sliver --
   unreadable, and far under the 24px target floor. Give the grid a real
   minimum width and let the frame scroll horizontally; the header row and
   the body scroll together because both are children of the same wrapper. */
@media (max-width: 640px) {
  /* .calendar-frame and .calendar-timegrid-wrap are the SAME element, so the
     min-width has to go on its children -- putting it on the wrapper itself
     just grows the scroll container and pushes the whole page sideways. The
     three rows below are the grid's full-width bands; sizing all three keeps
     the day headers, the untimed row and the hour body in column alignment
     as they scroll together. */
  .calendar-frame { overflow-x: auto; overflow-y: hidden; }
  .calendar-timegrid-wrap > .calendar-timegrid-header,
  .calendar-timegrid-wrap > .calendar-untimed-row,
  .calendar-timegrid-wrap > .calendar-timegrid-body { min-width: 700px; }
}


/* Admin dashboard: three fixed queues rather than auto-flowing cards.
   .dash-grid auto-fits, so a paperwork card could land between two visit
   cards depending on viewport width -- the page then reads as a pile
   instead of "signatures / visits / money". Each column is a stack, and
   they collapse to one column on narrow screens where three would be
   unreadable anyway. */
.dash-columns {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  align-items: start;
}
.dash-col {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
@media (max-width: 1100px) {
  .dash-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 760px) {
  .dash-columns { grid-template-columns: 1fr; }
}


/* Provider edit popup. It carries the whole caregiver profile, and it is laid
   out by .edit-lock-grid -- the same grid as the patient panel -- rather than
   by three fixed columns of its own: three separate runs of fields ended at
   three different heights, and the field you were reading lined up with
   nothing beside it. */
@media (max-width: 900px) {
  .provider-edit-panel { width: 94vw; }
}

/* The paperwork catalog: built-in forms and uploaded documents side by side
   in one box. Two columns rather than one stacked list, because the two
   halves are peers -- stacking them would reinstate the very hierarchy the
   merge exists to remove. */
.catalog-columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px 18px;
  align-items: start;
}
@media (max-width: 900px) {
  .catalog-columns { grid-template-columns: 1fr; }
}

/* The Access Levels popup. Wide, because the capability list is long and the
   point of opening it is to compare a tier's answers across it. */
.access-levels-columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 18px;
  align-items: start;
}
@media (max-width: 900px) {
  .access-levels-panel { width: 94vw; }
  .access-levels-columns { grid-template-columns: 1fr; }
}

/* Click-to-open explanations, replacing native title tooltips. */
.info-note { display: inline-block; vertical-align: middle; }
.info-note > summary {
  cursor: pointer;               /* pointer, not help: it opens something */
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.8rem;
  color: var(--muted);
}
.info-note > summary::-webkit-details-marker { display: none; }
.info-note > summary:hover { color: var(--text); }
.info-note > summary:focus-visible { outline: 2px solid var(--accent, #80a156); outline-offset: 2px; }
.info-note[open] > summary { color: var(--text); }
.info-note-body {
  margin-top: 6px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-subtle);
  font-size: 0.8rem;
  line-height: 1.45;
  max-width: 60ch;
}
/* A multi-step explanation. 60ch is right for a sentence and far too narrow
   for a six-step process -- the steps end up one word wide and nobody reads
   past the second. This one takes most of the page and lets each step have
   its own line, which is the whole point of numbering them.

   Block, not inline-block: an inline-block shrinks to its summary, so the
   panel could never be wider than the words "How a claim travels". */
.info-note-wide { display: block; }
.info-note-wide > .info-note-body {
  max-width: none;
  width: 80%;
  /* Below roughly a tablet, 80% of a narrow screen is not a readable
     column -- it is the same text with a wasted gutter. */
  min-width: min(100%, 46ch);
}
.info-steps {
  margin: 0;
  padding-left: 20px;
  display: grid;
  gap: 8px;
}
.info-steps > li { padding-left: 2px; }
/* The step's own headline, so the eye can find "where did the money go"
   without reading the four steps before it.
   FIRST-CHILD ONLY. As `.info-steps strong` this caught every emphasised
   word in the prose too, so "999", "277CA" and "835" each broke onto a line
   of their own mid-sentence and steps 4-6 read as if they had been shredded. */
.info-steps > li > strong:first-child { display: block; }
@media (max-width: 900px) {
  .info-note-wide > .info-note-body { width: 100%; }
}

/* A fieldset purely for its disabled semantics -- it must not introduce the
   browser's default border, padding or min-width, or wrapping the fields
   would visibly reflow the panel. */
.edit-lock {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}
/* Locked fields have to read as locked, or the Start editing button looks
   like it does nothing. */
.edit-lock[disabled] {
  opacity: 0.72;
}
.edit-lock[disabled] input,
.edit-lock[disabled] select,
.edit-lock[disabled] textarea {
  cursor: not-allowed;
}

/* --- Provider Visits tab -------------------------------------------------
   Built for a phone held one-handed at a front door: cards rather than a
   table, because a table of five columns on a 380px screen becomes five
   stacked unlabelled fragments, and tap targets big enough to hit without
   looking. */
.visit-section { margin-bottom: 22px; }
.visit-section > h3 { margin: 0 0 2px; }

/* The two standing panels on this page -- the live visit and the by-hand
   log -- share a shape so they read as siblings. Only the live one is tinted:
   it is the one with a clock running, and if both shouted neither would. */
.visit-box {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px;
  background: var(--surface);
}
.visit-box > h3 { margin-top: 0; }

.visit-live {
  border: 2px solid var(--accent, #80a156);
  border-radius: 12px;
  padding: 14px;
  margin-bottom: 22px;
  background: color-mix(in srgb, var(--accent, #80a156) 7%, transparent);
}

/* Same shape as .visit-live, danger-toned instead of accent-toned -- an
   outstanding bill is the one thing on the patient dashboard that is
   actually urgent (money owed, not just paperwork), so it sits above
   everything else and reads as unmistakably red rather than another card
   competing for attention. */
.pay-due-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  border: 2px solid var(--danger, #b3261e);
  border-radius: 12px;
  padding: 14px 16px;
  margin-bottom: 18px;
  background: color-mix(in srgb, var(--danger, #b3261e) 8%, transparent);
}
.pay-due-banner strong { color: var(--danger, #b3261e); }
.visit-live > h3 { margin: 0; }

.visit-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 12px;
  margin-top: 10px;
}

/* Full-width band label separating past / today / upcoming, matching the
   date-group ribbon the nurse portal already uses on /admin/visits-notes so
   the two apps read the same way. Three bands rather than one continuous
   list because "what am I doing today" is the question this page is opened
   for, and it was previously somewhere in the middle of the scroll. */
.visit-band-head {
  /* Neutral grey, NOT --bg-subtle -- that token is already a pale blue, so
     using it here made all three bands read as the same colour and lost the
     one distinction the bands exist to draw. */
  background: var(--surface-sunken);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  margin: 18px 0 0;
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--muted);
}

/* The band heading carries its own filter, so it is a row rather than a
   caption. */
.visit-band-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}
.visit-band-filter { display: flex; gap: 4px; }
.visit-band-filter .subtab-link { font-size: 0.75rem; padding: 3px 10px; }

.visit-band-head:first-of-type { margin-top: 10px; }
/* Today is the band with something to actually do in it, so it is the one
   that carries colour and full-strength text. */
.visit-band-today {
  background: var(--bg-subtle);
  border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
  border-left: 4px solid var(--accent);
  color: var(--text);
  font-weight: 700;
}
.visit-band-pager {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 10px;
}
.visit-card {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* Today is the only row that is actionable right now, so it is the only one
   that gets weight. */
.visit-card-today { border-color: var(--accent, #80a156); border-width: 2px; }
.visit-card-done { opacity: 0.66; }
/* A past visit nobody logged. Red because it is the one card here that means
   something is owed -- and only in the past band, since an upcoming visit is
   un-logged by definition and colouring those would drain the signal. Must
   come after the plain .visit-card rule above -- both have one-class
   specificity, and .visit-card's `border` shorthand otherwise wins on source
   order and silently overrides this back to grey. */
.visit-card-overdue {
  border-color: var(--danger, #b3261e);
  border-width: 2px;
  background: color-mix(in srgb, var(--danger, #b3261e) 5%, transparent);
}
/* Beats .visit-card-done's fade: an unlogged visit must not look retired. */
.visit-card-overdue.visit-card-done { opacity: 1; }

.visit-card-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 10px;
}
.visit-card-head h4 { margin: 2px 0 0; font-size: 1.05rem; }
.visit-card-day {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.visit-card-hours { text-align: right; white-space: nowrap; }
.visit-card-hours strong { display: block; font-size: 1.05rem; font-variant-numeric: tabular-nums; }
.visit-card-hours span { font-size: 0.72rem; color: var(--muted); }

.visit-card-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.visit-card-tasks ul { margin: 4px 0 0; padding-left: 18px; font-size: 0.85rem; }
.visit-card-tasks li { margin-bottom: 2px; }
.visit-card-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: auto;
  padding-top: 4px;
}

.visit-time-pair { display: flex; gap: 10px; }
.visit-time-pair > div { flex: 1; min-width: 0; }

/* Date + time in + time out, wrapping to two rows on a narrow phone rather
   than squeezing three date/time controls into 390px. */
.visit-when { display: flex; flex-wrap: wrap; gap: 10px; }
.visit-when > div { flex: 1 1 130px; min-width: 0; }

/* Role labels on a calendar visit card. Small, muted and uppercase: they are
   there to be read once, on the way to the name beside them, and a chip with
   the weight of a status badge would compete with the badge it labels. */
.visit-role-chip {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.visit-card-who { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* The patient row's chip replaces a "Patient:" label that had its own width
   rule; without this the name sits hard against the chip. */
.visit-card-row > .visit-role-chip { margin-right: 6px; }

/* A standing warning above a form, for the case where the form itself is the
   risky thing (hand-editing a priced invoice). Amber rather than red: this is
   a supported action with a consequence, not an error. */
.callout {
  border: 1px solid var(--border);
  border-left-width: 4px;
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 0.88rem;
  line-height: 1.45;
}
.callout-warn {
  background: var(--state-warn-soft);
  color: var(--state-warn-ink);
  border-color: var(--state-warn);
}

/* Numeric cell inputs inside an editable table: right-aligned and only as wide
   as the figures they hold, so a seven-column row still fits without the
   table scrolling on a laptop. */
.cell-num {
  width: 100%;
  max-width: 7.5rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.admin-table td > input { margin-bottom: 0; }

/* --- vital signs on the visit form ---
   Same border/radius/legend as .visit-checklist below, because they sit one
   above the other on the same form and a second visual style there would read
   as a second kind of thing. */
.vitals-field {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
  margin: 14px 0;
  min-width: 0;
}
.vitals-field > legend { font-weight: 600; font-size: 0.85rem; padding: 0 6px; }
/* 150px floor, not 260px like the checklist grid: these are number boxes a few
   characters wide, so two fit across a phone where a checkbox row would not. */
.vitals-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
}
.vital-input > label {
  display: block;
  font-size: 0.82rem;
  color: var(--muted);
  margin-bottom: 3px;
}
.vital-input-row { display: flex; align-items: center; gap: 6px; }
/* min-width:0 so the input can shrink inside the grid track rather than
   forcing the column wider than the screen -- number inputs have a wide
   default intrinsic width. */
.vital-input-row > input { flex: 1 1 auto; min-width: 0; }
.vital-unit { color: var(--muted); font-size: 0.85rem; white-space: nowrap; }

/* Readings on the admin note popup and PDF-facing views. Tabular figures so a
   column of blood pressures lines up on the digits. */
.vitals-readout dd { font-variant-numeric: tabular-nums; }
.vitals-alert {
  background: var(--state-warn-soft);
  color: var(--state-warn-ink);
  border-radius: 6px;
  padding: 6px 8px;
}

/* EVV clock-in/clock-out ticks. Green when that end of the visit was
   captured, red when it was missed -- one mark per end, so a reader counts
   two greens or spots the red without reading the words. */
.evv-legend { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.evv-legend .evv-tick:not(:first-child) { margin-left: 10px; }
.evv-tick { color: var(--state-good); font-weight: 700; }
.evv-tick-missing { color: var(--danger); }

/* --- the notification checklist --- */
.visit-checklist {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px;
  margin: 14px 0;
  min-width: 0;
}
.visit-checklist > legend {
  font-weight: 600;
  font-size: 0.85rem;
  padding: 0 6px;
}
.visit-checklist-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 6px;
}
.visit-check {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.85rem;
  font-weight: normal;
  cursor: pointer;
  line-height: 1.25;
}
.visit-check input { width: 18px; height: 18px; flex: none; }
.visit-check:has(input:checked) {
  border-color: var(--accent, #80a156);
  background: color-mix(in srgb, var(--accent, #80a156) 10%, transparent);
}

/* "No changes" is the answer most visits get, so it is the one thing on
   screen rather than the first of seventeen equal boxes: bigger target, its
   own line, and a subtitle saying it's the whole answer. */
.visit-check-clear {
  grid-column: 1 / -1;
  align-items: flex-start;
  padding: 12px;
  border-width: 2px;
}
.visit-check-clear span { display: flex; flex-direction: column; gap: 2px; }
.visit-check-clear strong { font-size: 0.95rem; }
.visit-check-clear small { color: var(--muted); font-size: 0.78rem; line-height: 1.3; }

/* The other sixteen, behind a disclosure. Closed, this is one row instead of
   about a thousand pixels of scroll between the caregiver and the Log button;
   open, it is the same list in the same order as the agency's paper form. */
.visit-concerns { margin-top: 8px; }
.visit-concerns > summary {
  cursor: pointer;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.85rem;
  font-weight: 600;
  list-style: none;
}
.visit-concerns > summary::-webkit-details-marker { display: none; }
.visit-concerns > summary::before { content: "+ "; color: var(--muted); }
.visit-concerns[open] > summary { margin-bottom: 8px; }
.visit-concerns[open] > summary::before { content: "\2212\00a0"; }

/* Abuse and 911 change what happens next and must not be tickable in a blur.
   Marked BEFORE they're ticked, not only after -- a warning that only appears
   once you've already ticked the box is not a warning. */
.visit-check-alarm {
  border-color: color-mix(in srgb, var(--danger, #b3261e) 45%, var(--border));
  box-shadow: inset 3px 0 0 var(--danger, #b3261e);
}
.visit-check-alarm:has(input:checked) {
  border-color: var(--danger, #b3261e);
  background: color-mix(in srgb, var(--danger, #b3261e) 12%, transparent);
}
.visit-check-detail { margin-top: 10px; }
.visit-comment-label { display: block; margin-top: 14px; }

/* Bigger targets for the standalone Visit Check-In page, which is opened
   one-handed straight after a visit rather than alongside a schedule. Same
   questions, same markup -- only the sizing differs, which is what let that
   page stop keeping its own hand-styled copy of the whole checklist. */
.visit-checklist-lg .visit-check { padding: 13px 12px; font-size: 1rem; }
.visit-checklist-lg .visit-check input { width: 24px; height: 24px; }
.visit-checklist-lg .visit-check-clear strong { font-size: 1.05rem; }
.visit-checklist-lg > .visit-concerns > summary { padding: 13px 12px; font-size: 0.95rem; }

/* On a narrow phone the patient's name and the time range cannot sit on one
   line, and the card was pushing the page sideways rather than wrapping --
   the clipped half being the times, which is the half you are checking. */
@media (max-width: 480px) {
  .visit-card-head { flex-direction: column; align-items: flex-start; gap: 2px; }
  .visit-card-hours { text-align: left; }
  .visit-card-hours strong { display: inline; margin-right: 6px; }
  .visit-card-actions > form, .visit-card-actions > button { flex: 1 1 auto; }
}

/* One person's paperwork: documents, checklist items and forms in one table
   rather than three columns. The Kind column is what lets them merge -- it
   keeps the distinction readable at a glance while costing one narrow column
   instead of three wide ones. */
.paperwork-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.paperwork-table {
  margin: 0;
  font-size: 0.78rem;
}
.paperwork-table th,
.paperwork-table td {
  padding: 3px 6px;
  vertical-align: top;
}
.paperwork-kind {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  white-space: nowrap;
}

/* A second line under the badge in the Next action cell -- what is known
   about that person's progress, or the control to take the step when it is
   the agency's. Indented from the badge above it so the pair reads as one
   answer rather than two stacked ones. */
.paperwork-subrow { margin: 3px 0 0; display: flex; justify-content: center; }
.paperwork-subrow form { margin: 0; }

/* Type, Status and the row actions sit centred in their columns. Left-aligned
   against a wide Item column they drifted apart into three ragged edges, and
   the eye had nothing to follow down the list. Item itself stays left: it is
   prose of varying length and centring it would be much worse. */
.paperwork-table th:not(:first-child),
.paperwork-table td:not(:first-child) { text-align: center; }
.paperwork-table td:not(:first-child) .table-actions { justify-content: center; }

/* The remove button is the one destructive control in the row and it sat
   flush against Confirm and the download arrow -- three targets in a row with
   nothing between them, which is how a mis-tap deletes somebody's paperwork.
   The gap is the guard. */
.paperwork-table .table-actions { gap: 10px; }
.paperwork-table .table-actions > form:last-child { margin-left: 8px; }
/* The three add-controls. Still three, because what you pick differs for each
   -- a document template, a checklist item, a form -- but they sit together
   as one group so adding anything starts in one place. */
.paperwork-add {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 8px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
}


/* --- Calendar visit colours ------------------------------------------------
   A planned visit is coloured by what actually happened, which lives on the
   timesheet entry rather than the schedule (see visit_evv_states). Carried on
   the left border so the chip's own text stays readable and the colours read
   as a column down the day. */
.cal-chip.cal-done      { border-left: 4px solid var(--state-good); }
.cal-chip.cal-missed    { border-left: 4px solid var(--state-missed); }
.cal-chip.cal-requested { border-left: 4px solid var(--state-warn); }
.cal-chip.cal-future    { border-left: 4px solid var(--state-neutral); }
.cal-chip.cal-unstaffed { border-left: 4px dashed var(--state-critical); }

/* Secondary encoding for the chip's state -- REQUIRED, not decoration.
   A chip carries only a time and a name, so before this the logging state
   was the 4px border's colour and nothing else. good vs critical is deutan
   dE 2.8 (see the state scale in :root): a red-green colourblind reader
   could not tell a clean visit from one that was never logged, and the
   page legend does not help per-chip. The glyph makes the state readable
   without colour. Keep it in sync with .cal-swatch's legend labels. */
.cal-chip::before {
  content: attr(data-state-glyph);
  font-weight: 700;
  font-size: 0.85em;
  line-height: 1;
  margin-right: 3px;
  opacity: 0.85;
}
.cal-chip.cal-done::before      { content: "\2713"; }  /* check -- it happened */
.cal-chip.cal-missed::before    { content: "\2717"; }  /* cross -- nobody logged it */
.cal-chip.cal-requested::before { content: "?"; }      /* asked for, no time set yet */
.cal-chip.cal-future::before    { content: "\00B7"; }  /* dot -- not yet due */
.cal-chip.cal-unstaffed::before { content: "\26A0"; }  /* warning -- nobody assigned */

.cal-legend {
  display: flex;
  /* Wraps. With nowrap the legend was a single unbreakable row, so on a
     phone it pushed the whole PAGE into horizontal scrolling -- the one
     mobile failure that makes every other layout look broken too, because
     the header and the content then disagree about where the right edge is.
     Six entries fit one line on a desktop anyway; this only changes what
     happens when they cannot. */
  flex-wrap: wrap;
  gap: 4px 14px;
  align-items: center;
  font-size: 0.72rem;
  color: var(--muted);
}
/* Names the strip as a key rather than leaving four coloured words floating
   next to the month label, where they read as filters you could click. */
.cal-legend-title {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.cal-legend > span {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}
.cal-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  display: inline-block;
}
/* The swatches reuse the same values, as backgrounds rather than borders, so
   a colour can never drift between the key and the thing it explains. */
.cal-swatch.cal-done      { background: var(--state-good); }
.cal-swatch.cal-missed    { background: var(--state-missed); }
.cal-swatch.cal-requested { background: var(--state-warn); }
.cal-swatch.cal-future    { background: var(--state-neutral); }
/* Hollow with a dashed edge, the same way the blocks are drawn: same red as
   a cancellation, distinguishable from it. */
.cal-swatch.cal-unstaffed { background: color-mix(in srgb, var(--state-critical) 25%, transparent); border: 1px dashed var(--state-critical); }
/* Not a visit state -- the blue of an ordinary calendar block, which is what
   a company event is drawn as. Named in the key so it stops reading as a
   fifth visit colour. */
.cal-swatch.cal-event     { background: rgba(91, 155, 240, 0.6); }

/* The quarter-hour nudges in a visit's popup. Small, inline and next to the
   time they change, so the control reads as part of the reading rather than
   as a form. */
.cal-nudge {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 14px;
  margin-top: 8px;
  font-size: 0.85rem;
}
.cal-nudge .label { color: var(--muted); }
.cal-nudge-group { display: inline-flex; align-items: center; gap: 4px; color: var(--muted); }
.cal-nudge-group strong { color: var(--text); font-variant-numeric: tabular-nums; min-width: 5.2rem; text-align: center; }
.cal-nudge-group .icon-btn { padding: 0 6px; font-size: 1rem; line-height: 1.4; }


/* Visit note popup sections. */
.visit-note-section { margin-top: 12px; }
.visit-note-section > h4 {
  margin: 0 0 4px;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.visit-note-section p { font-size: 0.88rem; }
.visit-note-kv { display: grid; grid-template-columns: 5.5rem 1fr; gap: 2px 10px; margin: 0; font-size: 0.85rem; }
.visit-note-kv dt { color: var(--muted); }
.visit-note-kv dd { margin: 0; }


/* Provider demographic filters -- folded away until wanted, because there are
   seventeen and the common case is still searching for a name. */
.provider-filters { width: 100%; margin-top: 6px; }
.provider-filters > summary {
  cursor: pointer;
  font-size: 0.8rem;
  color: var(--muted);
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.provider-filters > summary::-webkit-details-marker { display: none; }
.provider-filters > summary::before { content: "\25B8"; }
.provider-filters[open] > summary::before { content: "\25BE"; }
.provider-filters > summary:hover { color: var(--text); }

.provider-filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 8px 12px;
  margin-top: 10px;
}
.provider-filter-grid label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 0.72rem;
  color: var(--muted);
  font-weight: normal;
}
.provider-filter-grid select,
.provider-filter-grid input { font-size: 0.8rem; width: 100%; min-width: 0; }
.provider-filter-range { display: flex; gap: 6px; }
.provider-filter-actions { display: flex; gap: 8px; margin-top: 10px; }


/* The legend sits between the navigation on the left and the view controls on
   the right, taking the slack so the two control groups stay at the edges
   where they were. */
/* The key always takes its own line under the toolbar.
   It used to sit inline when it fitted, which made the header LAYOUT depend
   on how wide the current label happened to be: "Aug 30 - Sep 5, 2026" is
   wider than "September 2026", so switching week to month moved the legend
   from a second row up onto the first, and the controls jumped. A toolbar
   that rearranges itself when you change view reads as a glitch.

   Its own row costs one line and is identical in every view, at every width,
   for every label. */
.calendar-nav > .cal-legend {
  flex-basis: 100%;
  order: 10;
  margin-left: 0;
}

/* Patient edit popup: wide enough to show the record at once rather than as a
   single column you scroll. */
.edit-lock-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 4px 18px;
  align-items: start;
}
/* Each label travels with its own control. As bare siblings a grid would
   treat them as separate items and land a label in one column with its input
   in the next. */
.edit-field { display: flex; flex-direction: column; min-width: 0; }
/* Section heading inside an edit panel. These panels list 20+ fields for one
   person, and an undivided run of them is unreadable -- you cannot tell where
   the portal login stops and the clinical record starts. Spans the full grid
   width, and the rule under it does the separating so the headings can stay
   small. Was five repeated inline declarations per heading before. */
.edit-section-heading {
  grid-column: 1 / -1;
  font-weight: 600;
  font-size: 0.78rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 14px 0 2px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
/* The first heading sits flush against the top of the panel. */
.edit-section-heading:first-of-type { margin-top: 0; }
.edit-field > input, .edit-field > select, .edit-field > textarea { width: 100%; }
/* Section headings and the submit button span the full width rather than
   taking a column of their own. */
.edit-lock-grid > div:not(.edit-field),
.edit-lock-grid > button,
.edit-lock-grid > p { grid-column: 1 / -1; }
/* Yes/no answers side by side on a row of their own. Each one is a word wide;
   given a grid cell apiece they would sit under a column of text inputs and
   read as three more fields. */
.edit-checks {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin-top: 8px;
}
.edit-checks label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: normal;
}
@media (max-width: 700px) {
  .patient-edit-panel { width: 94vw; }
  .edit-lock-grid { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------------------
   Popup sizes.

   These had grown one at a time -- 360, 380, 420, 480, 560, 900, 80vw, 90vw --
   so two icons on the same row opened windows of different widths and the app
   felt assembled rather than designed. Three sizes now, chosen by what the
   popup CONTAINS rather than by how much happened to be in it the day it was
   written:

     .popup-record  a whole record -- edit or info for a person, the access
                    matrix, a plan of care. Every one of these is the same
                    width, so opening edit and then info does not resize the
                    window under you.
     .popup-action  one short thing: link a patient, add a fee, confirm.
                    Sized to its content because a 90vw box holding three
                    fields reads as a mistake.
     .popup-page    an entire page embedded in an iframe.
--------------------------------------------------------------------------- */
.visit-popover-panel.popup-record { width: 90vw; max-width: 1100px; }
.visit-popover-panel.popup-action { width: min(94vw, 420px); }
/* .popup-claim  a whole claim: its payer, its scrub findings, its filing
   deadline, its service lines and the follow-up actions. It was opening
   at popup-action width -- min(94vw, 420px), sized for "link a patient,
   add a fee, confirm" -- which left the service-line table scrolling
   inside a 420px column. 80% of the viewport, which is what a claim
   actually needs to be read side by side with its findings. */
.visit-popover-panel.popup-claim { width: 80vw; max-width: 1400px; }
.visit-popover-panel.popup-page {
  width: 95vw;
  max-width: none;
  height: 90vh;
  max-height: 90vh;
  overflow: hidden;
  padding: 0;
}
/* A column, not a block. The close button is floated (as it is in every other
   popup), and a block-level replaced element -- which is what an iframe is --
   may not overlap a float, so the browser pushed the iframe down by the height
   of the button. It was still 100% of the panel tall, so its last ~45px sat
   below the panel's bottom edge and was cut off by overflow:hidden: the end of
   the paperwork list could not be reached by scrolling, because the part of
   the iframe's own viewport holding it was outside the box.
   As a flex column the button takes its row and the iframe takes exactly what
   is left, which is why the iframe must not carry a height of its own. */
.visit-popover-panel.popup-page.open { display: flex; flex-direction: column; }
.popup-page > .visit-popover-close {
  float: none;
  align-self: flex-end;
  flex: none;
  margin: 2px 6px 0 0;
}
.popup-page > iframe {
  flex: 1 1 auto;
  /* Without this a flex item refuses to shrink below its content height, and
     a long page inside would push the bottom back out of the box. */
  min-height: 0;
  width: 100%;
  border: none;
  border-radius: 0 0 12px 12px;
  display: block;
}
@media (max-width: 700px) {
  .visit-popover-panel.popup-record { width: 94vw; }
  .visit-popover-panel.popup-claim { width: 94vw; }
}

/* Profile popups -- the panel a person's NAME opens, for a patient or a
   provider. It is a whole record, so it is popup-record width, and a record
   that wide read as one narrow ribbon of text down the left with two thirds
   of the panel empty. Sections sit side by side instead, collapsing to one
   column on a phone where three would be three characters wide. */
.profile-popup-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px 24px;
  align-items: start;
  margin-bottom: 18px;
}
.profile-popup-heading {
  font-weight: 600;
  font-size: 0.8rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: 6px;
}
@media (max-width: 700px) {
  .profile-popup-grid { grid-template-columns: 1fr; }
}

/* Month-cell chips: time above the name rather than one line that runs out of
   room and truncates the name -- which is the half you are reading the cell
   for. The cell has height to spare; it did not have width. */
.cal-chip-time {
  display: block;
  font-size: 0.62rem;
  line-height: 1.15;
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
}
.cal-chip-who {
  display: block;
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The follow-up card. Bordered in the alarm colour because it only appears
   when something needs doing -- there is no "quiet" version of it to confuse
   with a normal card. */
.dash-card-alert {
  border: 2px solid var(--danger, #b3261e);
  background: color-mix(in srgb, var(--danger, #b3261e) 4%, transparent);
}

/* The calendar's tinted grounds were mixed for a white page: over a near-black
   one they all but vanish, so the coloured left borders were carrying the
   whole signal alone. Slightly stronger tints in dark mode, applied to the
   tint only -- the border colours and the text token already adapt. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .cal-chip { background: rgba(91, 155, 240, 0.18); }
  :root:not([data-theme="light"]) .calendar-visit-block { background: rgba(91, 155, 240, 0.18); }
  :root:not([data-theme="light"]) .calendar-visit-block.real-visit.cal-done      { background: rgba(76, 175, 80, 0.20); }
  :root:not([data-theme="light"]) .calendar-visit-block.real-visit.cal-missed    { background: rgba(240, 144, 74, 0.22); }
  :root:not([data-theme="light"]) .calendar-visit-block.real-visit.cal-requested { background: rgba(230, 180, 30, 0.20); }
  :root:not([data-theme="light"]) .calendar-visit-block.real-visit.cal-future    { background: rgba(154, 165, 173, 0.18); }
  :root:not([data-theme="light"]) .calendar-visit-block.real-visit.cal-unstaffed { background-color: rgba(229, 103, 95, 0.22); }
}
:root[data-theme="dark"] .cal-chip { background: rgba(91, 155, 240, 0.18); }
:root[data-theme="dark"] .calendar-visit-block { background: rgba(91, 155, 240, 0.18); }
:root[data-theme="dark"] .calendar-visit-block.real-visit.cal-done      { background: rgba(76, 175, 80, 0.20); }
:root[data-theme="dark"] .calendar-visit-block.real-visit.cal-missed    { background: rgba(240, 144, 74, 0.22); }
:root[data-theme="dark"] .calendar-visit-block.real-visit.cal-requested { background: rgba(230, 180, 30, 0.20); }
:root[data-theme="dark"] .calendar-visit-block.real-visit.cal-future    { background: rgba(154, 165, 173, 0.18); }
:root[data-theme="dark"] .calendar-visit-block.real-visit.cal-unstaffed { background-color: rgba(229, 103, 95, 0.22); }

/* An open visit -- clocked in, never clocked out. Amber on the time itself:
   from a previous day it means somebody forgot, and the timesheet is still
   accruing. */
.visit-time-open { color: #8a6d00; font-weight: 600; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .visit-time-open { color: #e0b93a; }
}
:root[data-theme="dark"] .visit-time-open { color: #e0b93a; }

/* The visit is complete; the evidence has a hole in it. Green badge, red mark
   -- one glance says "logged" and "but". */
/* A mark on an otherwise-green badge: the visit happened, and this is the
   one thing about it worth a second look. Two of them, and the colour is the
   difference -- red for evidence that is missing, purple for times a person
   edited (the same purple status-manual uses for "a human touched this"). */
.evv-gap { color: var(--danger); font-weight: 700; margin-right: 2px; }
.evv-adjusted { color: var(--state-manual); font-weight: 700; margin-right: 2px; }

/* The key to those marks. Open, not behind a click: it is now the only
   thing on the page that breaks the visits down, and a breakdown nobody can
   see until they think to look for it is not a breakdown. */
/* Bottom margin so the last legend row does not sit flush against the
   filter row that follows it -- they read as one block without it. */
.evv-key { margin: 8px 0 18px; }
.evv-key-title { font-size: 0.82rem; color: var(--muted); }
/* The GRID LIVES ON THE LIST, not on each row. It used to sit on the li, which
   made every row its own grid container -- so `max-content` was measured
   against that row's own badge and nothing else. Four badges of four different
   widths therefore produced four different starting positions for the counts
   and the sentences, which is what it looked like: ragged.
   `display: contents` on the li promotes its three children to grid items of
   the list, so one set of column tracks is shared by every row and the widest
   badge sizes the column for all of them. Each badge still renders at its own
   width; only the track is shared. */
.evv-key-list {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: max-content 2.4rem 1fr;
  align-items: baseline;
  gap: 3px 6px;
}
.evv-key-list li { display: contents; }
.evv-key-list li > .status-badge { justify-self: start; }
.evv-key-list li > strong { font-variant-numeric: tabular-nums; text-align: right; }
@media (max-width: 700px) {
  .evv-key-list { grid-template-columns: max-content 2.4rem; }
  .evv-key-list li > .help-text { grid-column: 1 / -1; }
}

/* Names wrap rather than widening their column: a two-line row costs less
   than a gutter on every other row (see visits_notes.html's colgroup). */
.col-name { overflow-wrap: anywhere; }

/* Icon-only controls carry no button chrome: an emoji in a filled grey box
   reads as a toolbar, and a row of them reads as a toolbar rather than as
   actions belonging to that row. The hover state is what says it is
   clickable. */
.icon-btn {
  background: none;
  border: 1px solid transparent;
  color: inherit;
  box-shadow: none;
}
.icon-btn:hover { background: var(--bg-subtle); border-color: var(--border); }
.icon-btn-danger { color: var(--danger); }
.icon-btn-danger:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); }
/* Repeated here, like the danger pair above it, because this later
   `.icon-btn { color: inherit }` block outranks the earlier variant rules by
   source order alone -- which is why the tick stayed the same grey as the
   text beside it while the cross was correctly red. */
.icon-btn-approve { color: var(--state-good); }
.icon-btn-approve:hover { background: color-mix(in srgb, var(--state-good) 12%, transparent); }

/* --- Admin week grid: days down, hours across ---------------------------
   Day labels stay pinned while the 24-hour track scrolls sideways, so you
   never lose which row you are reading. */
.calendar-weekgrid-wrap { overflow: hidden; }
.calendar-weekgrid-scroll { overflow-x: auto; padding-bottom: 4px; }

.calendar-weekgrid-header {
  position: relative;
  height: 22px;
  margin-left: 64px;
  border-bottom: 1px solid var(--border);
}
.calendar-weekgrid-hour {
  position: absolute;
  top: 4px;
  font-size: 0.66rem;
  color: var(--muted);
  transform: translateX(-50%);
  white-space: nowrap;
}

.calendar-weekgrid-row {
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid var(--border);
}
.calendar-weekgrid-row.today { background: color-mix(in srgb, var(--accent) 6%, transparent); }

/* Sticky: the day has to stay readable however far right you scroll, which is
   the whole reason for putting days on the stable axis. */
.calendar-weekgrid-daylabel {
  position: sticky;
  left: 0;
  z-index: 3;
  flex: none;
  width: 64px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  text-decoration: none;
  color: inherit;
  background: var(--page-bg, #fff);
  border-right: 1px solid var(--border);
}
.calendar-weekgrid-daylabel:hover { background: var(--bg-subtle); }
.calendar-weekgrid-dow { font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.calendar-weekgrid-dom { font-size: 1rem; font-weight: 600; }
.calendar-weekgrid-row.today .calendar-weekgrid-dom {
  background: var(--accent);
  color: var(--badge-ink);
  border-radius: 50%;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Day and night, so an overnight or early-start visit is obvious without
   reading the hour labels. The boundaries are 7am and 7pm as fractions of the
   24-hour track -- 7/24 and 19/24 -- with hard stops, so they land exactly on
   the hour lines rather than fading across them.

   Deliberately faint. It is background: the visit blocks and their state
   colours have to stay the thing you see first, and a night band strong
   enough to notice on its own would compete with them. */
.calendar-weekgrid-track {
  position: relative;
  flex: none;
  background: linear-gradient(
    to right,
    var(--cal-night) 0,
    var(--cal-night) 29.1667%,
    var(--cal-day) 29.1667%,
    var(--cal-day) 79.1667%,
    var(--cal-night) 79.1667%,
    var(--cal-night) 100%
  );
}
:root {
  --cal-day: transparent;
  --cal-night: rgba(21, 44, 84, 0.055);
}
/* In dark mode the page is already dark, so night cannot be "darker" without
   turning to mud -- it is a touch lighter and cooler instead, which reads the
   same way round: the lit part of the day stands out from it. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --cal-day: transparent;
    --cal-night: rgba(120, 150, 210, 0.075);
  }
}
:root[data-theme="dark"] {
  --cal-day: transparent;
  --cal-night: rgba(120, 150, 210, 0.075);
}
.calendar-weekgrid-line {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--border);
  opacity: 0.5;
}
.calendar-weekgrid-now { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--danger); z-index: 2; }

/* Wide and short, so the vertical writing mode the old grid needed is
   explicitly undone. align-items must be flex-start: centred, a label wider
   than its block overflows BOTH edges and loses its first characters as well
   as its last. */
.calendar-weekgrid-block {
  writing-mode: horizontal-tb;
  padding: 2px 6px;
  min-height: 0;
  align-items: flex-start;
  justify-content: center;
}
.calendar-weekgrid-block .calendar-visit-who {
  width: 100%;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* What did not fit. Pinned to the right of the row so it cannot be mistaken
   for a visit at 11pm, and it goes to the day view, which has the room. */
.calendar-weekgrid-more {
  position: sticky;
  right: 6px;
  float: right;
  z-index: 2;
  display: inline-block;
  margin-top: 4px;
  padding: 2px 8px;
  font-size: 0.72rem;
  border-radius: 999px;
  background: var(--bg-subtle);
  border: 1px solid var(--border);
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
}
.calendar-weekgrid-more:hover { border-color: var(--accent); color: var(--accent); }

.calendar-weekgrid-untimed {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin-top: 10px;
}
.calendar-weekgrid-untimed-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}


/* ---- Insights ------------------------------------------------------- */
/* Findings lead the page because they are the only part that asks anyone to
   do something. A severity stripe rather than a coloured background: the
   card has to stay readable in both themes, and a tinted panel that works on
   white rarely works on near-black. */
.insight-h2 { font-size: 0.95rem; margin: 22px 0 8px; letter-spacing: 0.02em; }
.insight-clear { padding: 10px 12px; border-left: 3px solid var(--state-good); background: var(--surface-subtle); }

.insight-findings { display: flex; flex-direction: column; gap: 8px; }
.insight-finding {
  display: grid;
  grid-template-columns: minmax(72px, auto) 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--state-neutral);
  border-radius: 6px;
}
.insight-finding.is-critical { border-left-color: var(--state-critical); }
.insight-finding.is-warn     { border-left-color: var(--state-warn); }
.insight-finding.is-info     { border-left-color: var(--state-info); }
/* tabular-nums so the metrics form a straight edge down the column even
   though they are different lengths ("7", "94.2%", "$1,240.00"). */
.insight-finding-metric {
  font-size: 1.25rem; font-weight: 700; text-align: center;
  font-variant-numeric: tabular-nums; color: var(--text);
}
.insight-finding.is-critical .insight-finding-metric { color: var(--state-critical); }
.insight-finding.is-warn     .insight-finding-metric { color: var(--state-warn-ink, var(--state-warn)); }
.insight-finding-title { font-weight: 600; font-size: 0.86rem; }
.insight-finding-detail { margin: 2px 0 0; font-size: 0.78rem; color: var(--muted); }
.insight-finding-action { margin: 3px 0 0; font-size: 0.78rem; }
.insight-finding-go { white-space: nowrap; }

/* Bar charts: hand-built rather than a charting library, because the page
   needs two small comparisons and not a plotting runtime. */
.insight-chart {
  /* The trend-line segments below are sized against this, so it is a variable
     rather than a literal -- a segment that spans a column but not the gap
     leaves a break at every joint, which is exactly how the line first
     shipped looking disconnected. */
  --insight-chart-gap: 8px;
  display: flex; align-items: flex-end; justify-content: flex-start; gap: var(--insight-chart-gap);
  height: 132px; margin: 10px 0 4px;
  padding: 6px 4px 0;
  overflow-x: auto;                 /* narrow screens scroll the chart, not the page */
}
/* Capped rather than flex:1 across the container: with only six months
   of data a stretched column reads as a filled panel, not a bar. The
   chart stays left-aligned so adding months extends it rightward
   instead of silently rescaling every bar. */
.insight-bar-col { display: flex; flex-direction: column; align-items: center; gap: 4px;
                   min-width: 30px; max-width: 62px; flex: 1 1 40px; }
.insight-bar-stack { display: flex; align-items: flex-end; height: 100px; width: 100%; }
.insight-bar { width: 100%; border-radius: 3px 3px 0 0; min-height: 4px; position: relative; }
.insight-bar-billed { background: color-mix(in srgb, var(--accent) 26%, transparent); }
.insight-bar-demand { background: color-mix(in srgb, var(--accent) 55%, transparent); }
/* The collected portion sits INSIDE the billed bar, anchored to the bottom,
   so the unfilled remainder is exactly the money still owed. */
.insight-bar-fill { position: absolute; bottom: 0; left: 0; right: 0; background: var(--accent); border-radius: 0 0 3px 3px; }
.insight-bar-label { font-size: 0.66rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.insight-key { display: inline-block; width: 10px; height: 10px; border-radius: 2px; vertical-align: -1px; }
.insight-key-collected { background: var(--accent); }
.insight-key-billed { background: color-mix(in srgb, var(--accent) 26%, transparent); }
.insight-key-demand { background: color-mix(in srgb, var(--accent) 55%, transparent); }
/* --chart-2, not --state-warn. It is the same orange to look at, but a series
   colour rather than the app's "something is wrong" colour -- a warn-coloured
   line across a chart of normal trading reads as an alert. Defined in all
   three theme blocks already. */
.insight-key-trend { background: var(--chart-2); height: 3px; border-radius: 2px; vertical-align: 3px; }

/* The trailing-average line, drawn as one stretched segment per column. */
.insight-bar-stack { position: relative; }
/* The box spans centre-to-centre of two adjacent columns: that is one column
   width PLUS the flex gap between them, offset back by half a column plus the
   gap. With only `-50% / 100%` the box stops short by the gap, so each segment
   began 8px right of where the last one ended -- eleven small breaks that read
   as a dotted line, and endpoints that missed the bar centres. */
.insight-trend-seg {
  position: absolute;
  left: calc(-50% - var(--insight-chart-gap));
  top: 0;
  width: calc(100% + var(--insight-chart-gap));
  height: 100%;
  overflow: visible;
  pointer-events: none;
  z-index: 2;
}
.insight-trend-seg line {
  stroke: var(--chart-2);
  stroke-width: 2.5;
  /* Without this the 0-100 viewBox stretch distorts the stroke: wide columns
     get a hairline, narrow ones a slab. */
  vector-effect: non-scaling-stroke;
  /* Square-ish caps that still overlap cleanly at the joints, so consecutive
     segments read as one stroke rather than as beads. */
  stroke-linecap: round;
  stroke-linejoin: round;
}
.insight-trend-dot {
  position: absolute;
  left: 50%;
  width: 3px; height: 3px;
  margin: 0 0 -1.5px -1.5px;
  border-radius: 50%;
  background: var(--chart-2);
  pointer-events: none;
  z-index: 3;
}

/* ---------------------------------------------------------- Insights ---
   Every colour below is derived from an existing token with color-mix
   rather than declared as a new one. A new token would need a value in all
   three theme blocks (:root, the prefers-color-scheme query and
   [data-theme="dark"]) and would silently fall back to nothing if one were
   missed -- which is exactly how --surface-2 above ended up painting no
   background on this page's cards. Deriving from --accent/--surface means
   dark mode follows for free. */

/* Sections are cards so the page reads as four answers rather than one
   long column. Padding stays tight -- the ask was more professional, not
   roomier. */
.insight-section {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 12px 14px 14px;
  margin-bottom: 12px;
}
.insight-section-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 10px;
}
.insight-section-head .insight-h2 { margin: 0 0 6px; }
.insight-section-head .info-note { margin-bottom: 6px; }
/* Pushed to the far end of the section head: it belongs to this section and
   the two below it, and nothing above it. */
.insight-period { margin: 0 0 6px auto; }
.insight-period-label { font-size: 0.72rem; color: var(--muted); white-space: nowrap; }

/* The information affordance on a stat tile. Absolutely positioned so the
   grid's column sizing is untouched, and the open body floats over the
   strip instead of reflowing it -- an expanding tile would shove every
   other number on the row sideways mid-read. */
.dash-stat-wrap { position: relative; display: block; }
.dash-stat-wrap > .dash-stat { height: 100%; }
.stat-note { position: absolute; top: 3px; right: 5px; line-height: 1; }
.stat-note > summary { font-size: 0.7rem; opacity: 0.55; }
.stat-note > summary:hover,
.stat-note[open] > summary { opacity: 1; }
.stat-note > .info-note-body {
  position: absolute;
  right: -4px;
  top: 20px;
  z-index: 40;
  width: max-content;
  max-width: min(44ch, 76vw);
  background: var(--surface);
  box-shadow: var(--shadow-md);
  white-space: normal;
  text-align: left;
}
/* No left-anchoring override here, deliberately. `right: -4px` above pins the
   panel's RIGHT edge near the icon so it opens leftward, which is already what
   keeps the rightmost tile on screen. An earlier attempt to "fix" the right
   edge by flipping those tiles to `left: -4px` made them open rightward and
   pushed the last tile's panel 229px past the viewport -- measured, not
   guessed. If this needs revisiting, measure the bounding box at 1440px with
   the LAST tile open, not the middle one. */

.insight-chart-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 14px 0 6px;
}
.insight-chart-title { font-size: 0.78rem; font-weight: 600; color: var(--muted); }

/* Per-column readout. The bars used to carry a `title` attribute, which is
   unreachable on a touchscreen and cannot be copied -- the same objection
   test_ui_disclosure_and_history.py raises about the claim note. This shows
   on hover AND on keyboard focus, and the column carries an aria-label so a
   screen reader gets the value without either. */
.insight-bar-col { position: relative; outline: none; }
.insight-bar-readout {
  position: absolute;
  bottom: calc(100% - 14px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  padding: 3px 7px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  font-size: 0.7rem;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s var(--ease);
}
.insight-bar-col:hover .insight-bar-readout,
.insight-bar-col:focus-visible .insight-bar-readout { opacity: 1; }
.insight-bar-col:focus-visible { border-radius: var(--radius-sm); box-shadow: 0 0 0 2px var(--accent); }
.insight-bar { transition: filter 0.12s var(--ease); }
.insight-bar-col:hover .insight-bar { filter: brightness(1.08); }

@media (max-width: 640px) {
  /* The explanation panels are the thing most likely to run off a narrow
     screen, so they stop tracking their icon and just fill the width. */
  .stat-note > .info-note-body {
    position: fixed;
    left: 8px;
    right: 8px;
    top: auto;
    width: auto;
    max-width: none;
  }
}

@media (max-width: 640px) {
  /* The Go button drops under the text rather than squeezing the metric
     column to nothing. */
  .insight-finding { grid-template-columns: minmax(60px, auto) 1fr; }
  .insight-finding-go { grid-column: 2; justify-self: start; }
}

/* The application form's checkbox groups (areas covered, shifts). A plain
   column of ten checkboxes pushes the rest of the step off-screen on a phone,
   which is where most people apply for a caregiver job. */
/* The public application page, styled to the agency's own website rather
   than to the portal. Someone arriving from firstsupport-homecare.com should
   not feel handed off to different software halfway through applying, so the
   green ground, the white card, the logo and the two typefaces are lifted
   from that site (sampled from the live page: ground #2e6f40, card #fbfbfb,
   ink #2c2725, field border #ddd). Pinned rather than tokenised, and
   identical in both themes: this is the agency's public face, and it does
   not change colour with the reader's OS setting any more than their
   website does. Scoped under .apply-page so none of it reaches the portal. */
/* Breaks out of .page's 90%/max-width/padding so the green reaches the
   window edges -- the portal's container is right for the portal and wrong
   for a page pretending to be part of a marketing site. */
.page:has(> .apply-page) { width: 100%; max-width: none; padding: 0; }
.apply-page {
  /* The card stays white whatever theme the viewer is in, so the native
     controls inside it must be light too. Under a dark color-scheme the
     browser paints checkboxes and radios dark-on-dark, which reads as
     already ticked before anyone has touched them. accent-color then makes
     the ticked state the agency's green rather than the browser's blue. */
  color-scheme: light;
  accent-color: #2e6f40;
  --apply-green: #2e6f40;
  --apply-green-dark: #245732;
  --apply-card: #fbfbfb;
  --apply-ink: #2c2725;
  --apply-muted: #5f6b63;
  --apply-line: #dddddd;
  background: var(--apply-green);
  min-height: 100vh;
  margin: 0;
  padding: 28px 16px 56px;
  width: 100%;
  max-width: none;
  font-family: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
}
/* --- the website's header, rebuilt ------------------------------------ */
.apply-site-header {
  /* Full-bleed like the website's: .apply-page carries 28px/16px padding, so
     the header is pulled back out to the viewport edges. Without this the
     white bar floats inset with green showing down both sides, which is the
     single most obvious way this page looked like different software. */
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  background: #fff; padding: 20px 48px; margin: -28px -16px 30px;
  flex-wrap: wrap;
}
.apply-site-logo { flex: 0 0 auto; }
.apply-site-logo img { height: 130px; width: auto; display: block; }
.apply-site-nav {
  flex: 1 1 340px;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 22px;
}
.apply-site-nav a {
  color: var(--apply-ink); text-decoration: none; font-size: 0.95rem; font-weight: 500;
  padding: 3px 0; white-space: nowrap;
}
.apply-site-nav a:hover { color: var(--apply-green); }
.apply-site-nav a.is-current { color: var(--apply-green); font-weight: 700; }
.apply-site-cta { display: flex; align-items: center; gap: 18px; flex: 0 0 auto; }
.apply-site-phone { text-align: right; line-height: 1.25; }
.apply-site-phone span { display: block; font-size: 0.82rem; font-weight: 700; color: var(--apply-ink); }
.apply-site-phone a { color: var(--apply-green); font-weight: 700; text-decoration: none; white-space: nowrap; }
.apply-site-quote {
  background: var(--apply-green); color: #fff; text-decoration: none;
  padding: 13px 30px; border-radius: 999px; font-weight: 600; white-space: nowrap;
}
.apply-site-quote:hover { background: var(--apply-green-dark); }

/* --- the website's footer, same reasoning ----------------------------- */
.apply-site-footer { max-width: 1020px; margin: 34px auto 0; color: #dcebe1; }
/* The website footer opens with a short call to action above the columns; the
   portal footer now carries the same block so the two read as one site. */
.apply-footer-lede { text-align: center; padding: 0 0 26px; }
.apply-footer-eyebrow {
  display: block; text-transform: uppercase; letter-spacing: 0.14em;
  font-size: 0.72rem; font-weight: 700; color: #b9d6c4; margin-bottom: 8px;
}
.apply-footer-lede h3 {
  color: #fff; font-size: 1.5rem; font-weight: 800; margin: 0 0 8px;
  text-wrap: balance;
}
.apply-footer-lede p { color: #dcebe1; margin: 0 auto 16px; max-width: 52ch; line-height: 1.55; }
.apply-footer-cta { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.apply-footer-btn {
  background: #fff; color: var(--apply-ink); text-decoration: none;
  font-weight: 700; font-size: 0.9rem; padding: 10px 22px; border-radius: 999px;
}
.apply-footer-btn.is-ghost { background: transparent; color: #fff; box-shadow: inset 0 0 0 1px #ffffff66; }
.apply-footer-btn:hover { background: #eaf3ec; color: var(--apply-ink); }
.apply-footer-btn.is-ghost:hover { background: #ffffff1a; color: #fff; }
/* The same city links the website footer carries. */
.apply-footer-cities {
  text-align: center; font-size: 0.8rem; line-height: 2; color: #cfe3d6;
  margin: 22px 0 0; padding-top: 18px; border-top: 1px solid #ffffff26;
}
.apply-footer-cities a { color: #eaf3ec; text-decoration: none; }
.apply-footer-cities a:hover { color: #fff; text-decoration: underline; }
.apply-footer-legal a { color: #eaf3ec; }

/* ---- Footer, matching the website's shape ---------------------------------
   The site puts its footer columns inside a white rounded card with the logo,
   under a green call-to-action band. Rendering the same columns flat on green
   is the main thing that made this page feel like different software. */
.apply-footer-eyebrow {
  display: inline-block; text-transform: none; letter-spacing: 0;
  font-size: 0.9rem; font-weight: 600; color: #fff; margin-bottom: 14px;
  padding: 7px 20px; border-radius: 999px; border: 1px solid #ffffff66;
}
.apply-footer-lede h3 { font-size: 2.1rem; letter-spacing: -0.01em; }
.apply-footer-btn.is-dark { background: #2c2725; color: #fff; }
.apply-footer-btn.is-dark:hover { background: #1d1a19; color: #fff; }

.apply-footer-card {
  background: #fff; color: var(--apply-ink);
  border-radius: 26px; padding: 38px 44px; margin-top: 34px;
  display: grid; grid-template-columns: 220px 1fr; gap: 10px 40px; align-items: start;
}
.apply-footer-brand img { width: 168px; height: auto; display: block; }
.apply-footer-card .apply-footer-cols {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px; margin: 0;
}
.apply-footer-card .apply-footer-cols h4 { color: var(--apply-ink); font-size: 1.15rem; margin: 0 0 12px; font-weight: 700; }
.apply-footer-card ul { list-style: disc; margin: 0; padding-left: 20px; display: grid; gap: 8px; }
.apply-footer-card .apply-footer-cols li { color: #4a5450; font-size: 0.92rem; line-height: 1.5; }
.apply-footer-card .apply-footer-cols a,
.apply-footer-card .apply-footer-cols span { color: #4a5450; text-decoration: none; }
.apply-footer-card .apply-footer-cols a:hover { color: var(--apply-green); text-decoration: underline; }
.apply-footer-social { display: flex; gap: 10px; margin: 0 0 14px; }
.apply-footer-card .apply-footer-social a {
  width: 38px; height: 38px; border-radius: 9px; background: var(--apply-green);
  color: #fff; display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.8rem; font-weight: 700; line-height: 1; text-transform: lowercase;
  text-decoration: none; padding: 0;
}
.apply-footer-card .apply-footer-social a:hover { background: var(--apply-green-dark); color: #fff; }
.apply-footer-card .apply-footer-cities {
  grid-column: 1 / -1; border-top: 1px solid #e7e7e7; color: var(--apply-muted);
  margin-top: 26px; padding-top: 18px; text-align: left;
}
.apply-footer-card .apply-footer-cities a { color: var(--apply-green); }
.apply-footer-legal { margin-top: 22px; }

@media (max-width: 860px) {
  .apply-site-header { padding: 14px 20px; margin: -28px -16px 22px; }
  .apply-site-logo img { height: 82px; }
  .apply-footer-card { grid-template-columns: 1fr; padding: 28px 22px; border-radius: 20px; }
  .apply-footer-brand img { width: 128px; margin: 0 auto 6px; }
  .apply-footer-card .apply-footer-cols { grid-template-columns: 1fr 1fr; gap: 22px; }
  .apply-footer-lede h3 { font-size: 1.6rem; }
}
@media (max-width: 520px) {
  .apply-footer-card .apply-footer-cols { grid-template-columns: 1fr; }
  .apply-site-logo img { height: 62px; }
  .apply-footer-cta { flex-direction: column; }
  .apply-footer-btn { width: 100%; text-align: center; }
}
.apply-footer-cols {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 26px;
  border-top: 1px solid rgba(255, 255, 255, 0.22); padding-top: 26px;
}
.apply-footer-cols h4 { color: #fff; font-size: 0.95rem; margin: 0 0 10px; font-weight: 700; }
.apply-footer-cols a, .apply-footer-cols span {
  display: block; color: #dcebe1; text-decoration: none; font-size: 0.88rem;
  line-height: 1.7; word-break: break-word;
}
.apply-footer-cols a:hover { color: #fff; text-decoration: underline; }
.apply-footer-legal {
  border-top: 1px solid rgba(255, 255, 255, 0.22); margin: 24px 0 0; padding-top: 16px;
  font-size: 0.82rem; color: #cfe2d6; text-align: center;
}
@media (max-width: 860px) {
  .apply-site-header { margin: -28px -16px 22px; padding: 12px 18px; justify-content: center; }
  .apply-site-nav { order: 3; flex-basis: 100%; justify-content: flex-start; }
  .apply-footer-cols { grid-template-columns: repeat(2, 1fr); }
}

/* Each step reads as a titled block of fields, the way the website's form
   does -- an uppercase section label over a tinted panel, so eight steps do
   not arrive as one undifferentiated column of inputs. */
.apply-wizard legend {
  display: block; text-transform: uppercase; letter-spacing: 0.08em;
  font-size: 0.76rem; font-weight: 700; color: var(--apply-muted);
  margin: 0 0 12px; padding: 0;
}
.apply-step {
  background: #f2f4f3; border: 1px solid #e6eae8; border-radius: 14px;
  padding: 22px 24px 8px;
}
.apply-card {
  background: var(--apply-card);
  color: var(--apply-ink);
  max-width: 1020px;
  margin: 0 auto;
  border-radius: 22px;
  padding: 38px 44px 44px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.18);
}
.apply-card h1 {
  font-size: 2.1rem;
  line-height: 1.15;
  margin: 0 0 10px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--apply-ink);
}
/* The website pairs a bold sans headline with one word set in Lora italic.
   Copying the pairing, not just the colour, is most of why the two pages
   read as the same organisation. */
.apply-card h1 em { font-family: Lora, Georgia, serif; font-style: italic; font-weight: 500; }
.apply-card .apply-lede { color: var(--apply-muted); font-size: 1rem; line-height: 1.55; margin: 0 0 26px; max-width: 70ch; }

/* Dots on a rule, like the website's stepper -- a numbered text list read as
   a table of contents rather than progress. */
.apply-progress { display: flex; align-items: flex-end; margin: 0 0 30px; padding: 0; position: relative; }
.apply-progress::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 5px;
  height: 2px; background: var(--apply-line);
}
.apply-progress-fill {
  position: absolute; left: 0; bottom: 5px; height: 2px;
  background: var(--apply-green); transition: width 0.25s ease; width: 0;
}
.apply-progress-step {
  flex: 1; text-align: center; position: relative; z-index: 1;
  font-size: 0.7rem; line-height: 1.25; color: var(--apply-muted);
  padding-bottom: 18px; border: none; background: none;
}
.apply-progress-step::after {
  content: ""; position: absolute; left: 50%; bottom: 0; width: 12px; height: 12px;
  transform: translateX(-50%); border-radius: 50%;
  background: #9aa5a0; border: 2px solid var(--apply-card);
}
.apply-progress-step.is-current { color: var(--apply-green); font-weight: 700; }
.apply-progress-step.is-current::after { background: var(--apply-green); }
.apply-progress-step.is-done::after { background: var(--apply-green); }

.apply-card .apply-section-title {
  text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.72rem;
  font-weight: 700; color: var(--apply-muted); margin: 0 0 14px;
}
/* Two columns, as on the website. Collapses to one on a phone, which is
   where a lot of caregivers will actually fill this in. */
/* Two columns, as on the website. Each label+control lives in .apply-field so
   the pair travels together -- as bare siblings the grid would put a label in
   one cell and its own input in the next. Anything that is not a plain field
   (checkbox blocks, notes, the consent rows) spans both. */
.apply-step { display: grid; grid-template-columns: 1fr 1fr; gap: 0 22px; }
.apply-step > :not(.apply-field) { grid-column: 1 / -1; }
.apply-field { min-width: 0; }
.apply-field.apply-wide, .apply-wide { grid-column: 1 / -1; }
@media (max-width: 720px) {
  .apply-step { grid-template-columns: 1fr; }
  .apply-card { padding: 26px 20px 32px; border-radius: 16px; }
  .apply-card h1 { font-size: 1.6rem; }
  .apply-progress-step { font-size: 0.6rem; }
}
.apply-card label { display: block; font-size: 0.85rem; font-weight: 600; color: var(--apply-ink); margin: 0 0 5px; }
.apply-card input[type=text], .apply-card input[type=email], .apply-card input[type=tel],
.apply-card input[type=date], .apply-card input[type=number], .apply-card input[type=password],
.apply-card select, .apply-card textarea {
  width: 100%; padding: 11px 13px; margin-bottom: 4px;
  border: 1px solid var(--apply-line); border-radius: 9px;
  background: #fff; color: var(--apply-ink); font-size: 0.95rem; font-family: inherit;
}
.apply-card input:focus, .apply-card select:focus, .apply-card textarea:focus {
  outline: 2px solid color-mix(in srgb, var(--apply-green), transparent 55%);
  outline-offset: 1px; border-color: var(--apply-green);
}
.apply-card .help-text { color: var(--apply-muted); font-size: 0.78rem; margin: 0 0 14px; }
.apply-checks { display: flex; flex-direction: column; gap: 9px; margin: 4px 0 14px; }
.apply-checks label { display: flex; align-items: flex-start; gap: 8px; font-weight: 400; font-size: 0.9rem; }
.apply-checks input { margin-top: 3px; }
.apply-wizard fieldset { border: none; padding: 0; margin: 0; }

/* Paired fields, the way the website sets first/last name and city/state on
   one row. .apply-step is already a two-column grid, but a pair has to stay a
   pair even when it sits beside a full-width block, so it carries its own. */
.apply-two { display: grid; grid-template-columns: 1fr 1fr; gap: 0 22px; }

/* A question with a fixed set of answers. Its legend is a field label, not a
   section heading, so it has to outrank .apply-wizard legend above -- which
   would otherwise render every question as small uppercase section text. */
.apply-card .apply-choice { margin: 0 0 14px; }
.apply-card .apply-choice > legend {
  text-transform: none; letter-spacing: 0; font-size: 0.85rem;
  font-weight: 600; color: var(--apply-ink); margin: 0 0 7px; padding: 0;
}
/* Options stack one per line, as Forminator renders them on the website. */
.apply-card .apply-radio {
  display: flex; align-items: flex-start; gap: 8px;
  font-weight: 400; font-size: 0.9rem; margin: 0 0 7px;
}
.apply-card .apply-radio input { margin-top: 3px; flex: 0 0 auto; }
.apply-card .apply-radio input[type=checkbox],
.apply-card .apply-radio input[type=radio] { width: 16px; height: 16px; cursor: pointer; }

/* A group that needs at least one answer, once somebody has tried to leave
   it empty. Nothing is marked until then -- a form that greets you in red
   is a form people abandon. */
.apply-choice.has-error > legend { color: #a5322b; }
.apply-choice.has-error .apply-checks { border-left: 2px solid #a5322b; padding-left: 10px; }

/* The server's own complaint, inside the card where the answers are, not
   above the page header where a returning applicant scrolls straight past
   it. */
.apply-error {
  background: #fdecea; border: 1px solid #f3b7b1; color: #8c2b23;
  border-radius: 10px; padding: 12px 14px; margin: 0 0 20px;
  font-size: 0.9rem; line-height: 1.5;
}

.apply-note { color: var(--apply-muted); font-size: 0.82rem; line-height: 1.5; margin: 2px 0 14px; }

/* A date field that looks like one. The native control gives no hint that it
   holds a calendar -- it reads as an empty text box until you find the small
   icon -- so the field carries a visible calendar mark and a pointer, and the
   script opens the picker from anywhere in it. */
.apply-date { position: relative; }
.apply-date input[type=date] { cursor: pointer; padding-right: 40px; }
.apply-date::after {
  content: ""; position: absolute; right: 14px; bottom: 17px;
  width: 16px; height: 16px; pointer-events: none; opacity: 0.55;
  background-color: var(--apply-green);
  -webkit-mask: var(--apply-cal-icon) center / contain no-repeat;
  mask: var(--apply-cal-icon) center / contain no-repeat;
}
.apply-page {
  --apply-cal-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'%3E%3Crect x='3' y='5' width='18' height='16' rx='2'/%3E%3Cpath d='M3 10h18M8 3v4M16 3v4'/%3E%3C/svg%3E");
}
/* Chrome draws its own indicator on top of the mark above; hide it and let
   the click handler open the picker instead. */
.apply-date input[type=date]::-webkit-calendar-picker-indicator {
  opacity: 0; cursor: pointer; position: absolute; right: 0; width: 40px; height: 100%;
}

/* The voluntary block, set apart from everything the decision is made on, so
   nobody reads it as one more question they are required to answer. */
.apply-eeo { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--apply-line); }
.apply-eeo h3 {
  text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.76rem;
  font-weight: 700; color: var(--apply-muted); margin: 0 0 8px;
}
@media (max-width: 720px) { .apply-two { grid-template-columns: 1fr; } }

.apply-nav { display: flex; gap: 10px; margin-top: 26px; }
.apply-card .apply-nav button {
  font-family: inherit; font-size: 0.95rem; font-weight: 700;
  padding: 11px 26px; border-radius: 999px; border: 1px solid transparent; cursor: pointer;
}
.apply-card .apply-nav button[type=button]#applyNext,
.apply-card .apply-nav button[type=submit] { background: var(--apply-green); color: #fff; }
.apply-card .apply-nav button#applyNext:hover,
.apply-card .apply-nav button[type=submit]:hover { background: var(--apply-green-dark); }
.apply-card .apply-nav button#applyBack { background: #fff; color: var(--apply-ink); border-color: var(--apply-line); }
.apply-footnote { text-align: center; color: #d8e6dc; font-size: 0.8rem; margin: 22px auto 0; max-width: 1020px; }
.apply-footnote a { color: #fff; }

/* Pager under a dashboard card. Anchored to the card rather than the page so
   paging does not scroll the reader back to the top of the dashboard. */
.dash-pager {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

/* The patient's timesheet table. Kept deliberately quiet -- it sits inside a
   dashboard card that already has a border and a heading, so a second boxed
   object inside it would read as two things rather than one. */
.timesheet-table { margin: 0; }
.timesheet-table th { font-weight: 600; }
/* Hours line up and read down the column; tabular figures stop "10h" and
   "9h 30m" from wobbling against each other. */
.timesheet-hours {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  font-weight: 600;
}
.timesheet-table tbody tr:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }
/* A patient's own comment hangs under its visit rather than boxed beside it,
   and the top border is removed so it reads as part of the row above. */
.timesheet-note-row td { border-top: none; padding-top: 0; }
.timesheet-note-row:hover { background: none; }
@media (max-width: 640px) {
  /* In stacked-card mode the hours would otherwise sit hard right, away from
     their own label. */
  .timesheet-hours { text-align: left; }
}

/* There used to be a second, underline tab style here (.subtab-row-underline
   / .subtab / .subtab-count with .is-active), used by exactly one page. It
   redefined .subtab-row itself -- same selector as the pill row 2,200 lines
   above, same specificity -- so the later rule won property by property and
   broke both designs: the five pill rows silently took gap:18px instead of
   the 4px they are padded for, and the underline row inherited the pill
   card's background, border, radius, shadow and centring, which is precisely
   what its own comment said it must not look like.

   Resolved by having one tab style rather than two. /admin/visits now uses
   .subtab-link like the other five tabbed pages, so tab position, styling and
   the active-state class name (`active`, never `is-active`) are the same
   everywhere. If an underline variant is ever wanted again, it needs its own
   container class -- never .subtab-row -- and must reset the card properties
   explicitly instead of relying on cascade order. */

/* An access-levels cell the agency has overridden away from the tier default.
   Ringed rather than recoloured: the tick/dash already carries the value, so
   this only has to say "somebody changed this one". */
.icon-btn.is-override { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 4px; }

/* Native time inputs need room for the AM/PM segment. Squeezed into a narrow
   column the meridiem is clipped: the field then looks 24-hour and the part
   you need to click is not reachable. */
input[type="time"] { min-width: 150px; }
input[type="date"] { min-width: 150px; }

/* Count of applications waiting on a decision, sat top-right of the tab it
   belongs to. Red because it is a queue with people in it: somebody applied
   and is waiting to hear back, and the number should read as owed rather than
   as decoration. Hidden entirely at zero -- a grey "0" is noise on every page
   an agency ever looks at. */
.subtab-link .subtab-count {
  display: inline-block;
  vertical-align: super;
  margin-left: 4px;
  min-width: 16px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--state-critical, #c62828);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

/* A settings gear at the end of a subtab row. Pinned to the right edge rather
   than laid out with the tabs, so the tabs stay centred on the page. Below the
   wrap breakpoint it rejoins the flow, because an absolutely-positioned icon
   over a two-line tab row overlaps it. */
.subtab-settings {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
}
@media (max-width: 640px) {
  .subtab-settings { position: static; transform: none; }
}

/* Two-up settings pages (Payment Settings). The sections are short and
   independent, so a single column left most of a wide screen empty and
   pushed the rates below the fold. Explicit columns rather than an
   auto-flowing grid so the pairing is deliberate: collecting money on one
   side, the rates that decide the amounts on the other. */
.settings-two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  align-items: start;
}
.settings-col { display: flex; flex-direction: column; gap: 10px; }
.settings-two-col .form-section { margin-bottom: 0; }
@media (max-width: 900px) {
  .settings-two-col { grid-template-columns: 1fr; }
}

/* Caregivers | Nurses inside the pay-rates form. auto-fit rather than a
   fixed 1fr 1fr because this form now lives in a half-width column --
   it has to collapse on its own container's width, which a page-level
   media query can't see. */
.rate-role-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 16px;
}

/* "Where do I get this?" steps, on every setting whose value comes from
   somewhere outside this app (Stripe dashboard, Google account, NPPES,
   a clearinghouse). Collapsed by default -- it is read once during setup
   and never again, so it must not crowd the field it explains. */
.setup-help { margin: 6px 0 10px; }
.setup-help > summary {
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--accent);
  list-style: none;
}
.setup-help > summary::-webkit-details-marker { display: none; }
.setup-help > summary::before { content: "? "; }
.setup-help > summary:hover { text-decoration: underline; }
.setup-help ol { margin: 6px 0 0; padding-left: 20px; }
.setup-help li { font-size: 0.8rem; color: var(--muted); margin-bottom: 5px; line-height: 1.45; }
.setup-help li code {
  font-size: 0.75rem;
  background: var(--surface-subtle);
  padding: 1px 4px;
  border-radius: 3px;
}

/* Caregiver application review (/admin/applications). The panel is read once,
   to make one decision, so it is laid out as the six steps the applicant
   actually filled in rather than as a flat list of stored keys -- two columns
   of sections, because a single column of 25 rows pushed the next applicant
   off the screen. */
.app-review-grid {
  /* Columns rather than a 2-track grid: the sections are different heights,
     and a grid leaves the short one's row half empty. Flowing them balances
     the two columns instead. */
  columns: 2;
  column-gap: 26px;
  margin: 6px 0 2px;
}
.app-review-section {
  break-inside: avoid;
  margin-bottom: 10px;
}
@media (max-width: 700px) {
  .app-review-grid { columns: 1; }
}
.app-review-section > h4 {
  margin: 0 0 3px;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.app-review-kv {
  display: grid;
  grid-template-columns: 10.5rem minmax(0, 1fr);
  gap: 1px 10px;
  margin: 0;
  font-size: 0.85rem;
}
.app-review-kv dt { color: var(--muted); }
.app-review-kv dd { margin: 0; overflow-wrap: anywhere; }

/* ---------------------------------------- the agency at a glance panels --- */
/* Four compositions, one question each. Colour is assigned by the job it does
   (dataviz method): identity for the payer mix, so a validated categorical
   set; status for the EVV split, so the app's own reserved --state-* steps,
   which never carry meaning without their label beside them; emphasis for
   client movement -- one hue and a gray, because "started" is the series that
   matters and "ended" is context; and a single hue for the meter, which is
   one ratio against a limit.

   The four categorical steps are the validated slots 1-4 (blue, orange, aqua,
   yellow), stepped for each surface rather than flipped. Run through the six
   checks on both surfaces: lightness band, chroma floor, adjacent-pair CVD
   (worst 9.1 light / 8.4 dark, target 8), normal-vision separation (22.9 /
   19.8, floor 15) and contrast. Aqua and yellow sit under 3:1 on white, which
   is why every segment is named with its count in the key below it -- colour
   never carries the identity alone. */
.insight-panels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 14px;
  margin-top: 12px;
}
.insight-panel {
  margin: 0;
  padding: 12px 14px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  min-width: 0;
}
.insight-panel figcaption { display: block; margin-bottom: 10px; }
.insight-panel-title { display: block; font-weight: 600; font-size: 0.85rem; }
.insight-panel-sub { display: block; font-size: 0.76rem; color: var(--muted); margin-top: 2px; }

/* A part-to-whole bar. Segments are separated by a 2px surface gap rather
   than a border -- a border draws a line that reads as data. */
.insight-split { display: flex; gap: 2px; height: 14px; border-radius: 4px; overflow: hidden; }
.insight-split-seg { display: block; min-width: 3px; }

.insight-panel-key { list-style: none; margin: 10px 0 0; padding: 0; display: grid; gap: 3px; }
.insight-panel-key li { display: flex; align-items: center; gap: 6px; font-size: 0.78rem; color: var(--muted); }
.insight-panel-key strong { margin-left: auto; color: var(--text); font-variant-numeric: tabular-nums; }

/* Started above the line, ended below it: the reader is comparing direction,
   which is the one job a diverging arrangement does better than two bars. */
.insight-movement { display: flex; align-items: stretch; gap: 4px; height: 96px; }
.insight-movement-col { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.insight-movement-up, .insight-movement-down { flex: 1; display: flex; }
.insight-movement-up { align-items: flex-end; }
.insight-movement-up > span,
.insight-movement-down > span { width: 100%; border-radius: 3px 3px 0 0; background: var(--viz-started); }
.insight-movement-down > span { border-radius: 0 0 3px 3px; background: var(--viz-ended); }
/* Hairline, solid, one shade off the surface -- a dashed rule here would read
   as a threshold. */
.insight-movement-axis { height: 1px; background: var(--border); }
.insight-movement-label {
  text-align: center; font-size: 0.66rem; color: var(--muted);
  margin-top: 4px; font-variant-numeric: tabular-nums;
}

.insight-meter {
  height: 14px; border-radius: 7px; background: var(--bg-subtle);
  overflow: hidden; border: 1px solid var(--border);
}
.insight-meter-fill { display: block; height: 100%; background: var(--seq-550); }
.insight-meter-fill.is-tight { background: var(--state-warn); }
.insight-panel-figure {
  margin: 8px 0 4px; font-size: 1.7rem; font-weight: 600; line-height: 1;
}
.insight-panel-figure span { font-size: 0.9rem; font-weight: 500; color: var(--muted); margin-left: 2px; }

/* Identity slots, light surface. */
:root {
  --viz-cat-1: #2a78d6;
  --viz-cat-2: #eb6834;
  --viz-cat-3: #1baf7a;
  --viz-cat-4: #eda100;
  --viz-started: #2a78d6;
  --viz-ended: #9aa5ad;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --viz-cat-1: #3987e5;
    --viz-cat-2: #d95926;
    --viz-cat-3: #199e70;
    --viz-cat-4: #c98500;
    --viz-started: #3987e5;
    --viz-ended: #6b7681;
  }
}
:root[data-theme="dark"] {
  --viz-cat-1: #3987e5;
  --viz-cat-2: #d95926;
  --viz-cat-3: #199e70;
  --viz-cat-4: #c98500;
  --viz-started: #3987e5;
  --viz-ended: #6b7681;
}
.insight-payer-1 { background: var(--viz-cat-1); }
.insight-payer-2 { background: var(--viz-cat-2); }
.insight-payer-3 { background: var(--viz-cat-3); }
.insight-payer-4 { background: var(--viz-cat-4); }
.insight-tone-good { background: var(--state-good); }
.insight-tone-warn { background: var(--state-warn); }
.insight-tone-critical { background: var(--state-critical); }
.insight-key-started { background: var(--viz-started); }
.insight-key-ended { background: var(--viz-ended); }

/* --- "type Edit to apply" confirmation (see _db_edit_confirm.html) ---
   Its own backdrop and panel rather than a second use of .visit-popover-panel:
   it opens OVER a popup that is already open (the claim follow-up modal, the
   embedded billing page), and the shared panel is a single node that one of
   those is already occupying. z-index sits one layer above the row modal's
   60/61 for the same reason. */
.db-edit-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: 70;
}
.db-edit-backdrop.open { display: block; }
.db-edit-panel {
  display: none;
  position: fixed;
  /* Top-anchored, not centred -- same iOS on-screen-keyboard reason as
     .visit-popover-panel, and this panel always has a focused input. */
  top: 10vh;
  left: 50%;
  transform: translateX(-50%) translateZ(0);
  z-index: 71;
  width: min(94vw, 460px);
  max-height: 80vh;
  overflow-y: auto;
  background-color: var(--surface);
  border: 1px solid var(--state-critical);
  border-radius: 12px;
  padding: 18px 20px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
}
.db-edit-panel.open { display: block; }
.db-edit-panel input[type="text"] { width: 100%; }
.db-edit-what {
  margin: 0 0 10px;
  padding: 8px 10px;
  border-radius: 8px;
  background: var(--state-critical-soft);
  color: var(--state-critical-ink);
  font-size: 0.88rem;
  line-height: 1.45;
  font-weight: 600;
}
.db-edit-what:empty { display: none; }
.db-edit-error { margin: 4px 0 0; font-size: 0.82rem; color: var(--danger); }
.db-edit-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }

/* A callout one step above .callout-warn: the amber one means "supported
   action with a consequence", this one means "the record itself is about to
   disagree with something already sent out". */
.callout-danger {
  background: var(--state-critical-soft);
  color: var(--state-critical-ink);
  border-color: var(--state-critical);
}

/* --- editable service lines (admin/_service_lines_fragment.html) ---
   Cards rather than table rows: each line is its own <form> (a <form> cannot
   be a child of <tr>), and the modal these open in is narrower than the nine
   columns the read-only breakdown uses. */
.service-line-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 10px;
  background: var(--surface);
}
/* One line, left to right. It was date-left / everything-else-right, which on
   a 1100px panel put a lake of empty card between them and still wrapped the
   total onto a second line when the caregiver's name was long. Small type,
   because this is the line you read to identify the row, not to work from. */
.service-line-head {
  display: flex;
  align-items: baseline;
  gap: 4px 10px;
  flex-wrap: nowrap;
  margin-bottom: 8px;
  font-size: 0.78rem;
  color: var(--muted);
}
.service-line-head > * { flex: none; white-space: nowrap; }
.service-line-head strong { color: var(--text); font-size: 0.85rem; }
/* The only part of arbitrary length, so the only part that gives way: it
   ellipsises and the figures after it keep their place on the row. */
.service-line-who {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.service-line-total { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text); }
@media (max-width: 560px) {
  /* Below this there is no row left to keep things on. */
  .service-line-head { flex-wrap: wrap; }
}
.service-line-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px 10px;
}
/* A field that takes the whole row rather than a 140px slice of it -- the
   service name and the reason are both free text of unpredictable length. */
.service-line-full { grid-column: 1 / -1; }
.service-line-grid label { display: block; font-size: 0.8rem; margin-bottom: 2px; }
.service-line-grid input, .service-line-grid select { width: 100%; margin-bottom: 0; }
.service-line-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 10px; }

/* --- boxed digits (see _boxed_digits.html) ---
   A row of one-character cells sitting over the printed boxes on a tax form.
   Sized in ch and capped by the field's own width so nine of them fit the
   space the PDF actually leaves, at any zoom. */
.digit-boxes {
  display: flex; align-items: center; gap: 2px;
  width: 100%; height: 100%;
}
.digit-box {
  flex: 1 1 0; min-width: 0; height: 100%;
  padding: 0; text-align: center;
  font-variant-numeric: tabular-nums;
  font-size: clamp(.6rem, 1.1vw, .95rem);
  border: 1px solid var(--border); border-radius: 2px;
  background: var(--surface); color: var(--text);
}
.digit-box:focus { outline: 2px solid var(--accent, #80a156); outline-offset: -1px; }
.digit-sep { flex: none; color: var(--muted); font-size: .8em; padding: 0 1px; }

/* --- vital signs over time (see _vitals_history.html) ---
   Four small charts, drawn as inline SVG on a percentage grid. No library and
   no resize handler: the viewBox stretches, and the markers are positioned in
   percent over it. */
.vitals-history { display: flex; flex-direction: column; gap: 14px; }
.vitals-history-head { display: flex; flex-direction: column; gap: 3px; }
.vitals-chart-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px 18px;
}
.vitals-chart { margin: 0; display: flex; flex-direction: column; gap: 5px; }
.vitals-chart figcaption {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 8px; flex-wrap: wrap;
}
.vitals-chart-title { font-weight: 600; font-size: 0.9rem; }
/* The plot is the positioning context for both the SVG and the markers. */
.vitals-chart-plot {
  position: relative;
  height: 110px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  padding: 0;
}
.vitals-chart-svg { display: block; width: 100%; height: 100%; overflow: visible; }
.vitals-band { fill: var(--state-good-soft, rgba(63,122,82,.14)); }
.vitals-line { fill: none; stroke: var(--chart-1, #2f6f8f); stroke-width: 1.5; stroke-linejoin: round; }
.vitals-dot {
  position: absolute;
  width: 6px; height: 6px; margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: var(--surface);
  border: 1.5px solid var(--chart-1, #2f6f8f);
  pointer-events: auto;
}
.vitals-dot-out {
  width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px;
  background: var(--danger); border-color: var(--danger);
}
.vitals-axis {
  position: absolute; right: 4px;
  font-size: 0.66rem; color: var(--muted);
  font-variant-numeric: tabular-nums;
  background: var(--surface); padding: 0 2px; border-radius: 2px;
}
.vitals-axis-high { top: 2px; }
.vitals-axis-low { bottom: 2px; }
.vitals-chart-dates {
  display: flex; justify-content: space-between;
  font-size: 0.7rem; color: var(--muted);
}

/* Caption on one line: the unit rides in the title and the range sits right,
   both refusing to wrap. "Blood pressure" + "normal 90/60-180/110 mmHg" was
   the one caption long enough to break, which dropped that chart a row below
   its three neighbours. */
.vitals-chart figcaption { flex-wrap: nowrap; }
.vitals-chart-title { white-space: nowrap; }
.vitals-chart-unit { font-weight: 400; color: var(--muted); font-size: 0.82em; }
.vitals-chart-range {
  font-size: 0.72rem; color: var(--muted); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; min-width: 0;
}

/* Two lines on one chart need two colours. Systolic and diastolic were both
   drawn in --chart-1, so which was which could only be inferred from the fact
   that one is always above the other -- which stops being true on exactly the
   reading somebody is looking for. */
.vitals-line.vitals-series-1 { stroke: var(--chart-1); }
.vitals-line.vitals-series-2 { stroke: var(--chart-2); }
.vitals-dot.vitals-series-1 { border-color: var(--chart-1); }
.vitals-dot.vitals-series-2 { border-color: var(--chart-2); }
/* Out of range beats the series colour: at a glance the question is "is
   anything wrong", not "which line was that". */
.vitals-dot-out { background: var(--danger); border-color: var(--danger); }

.vitals-legend { display: flex; gap: 10px; align-items: center; }
.vitals-legend-item { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.vitals-legend-dot {
  width: 7px; height: 7px; border-radius: 50%;
  border: 1.5px solid currentColor; background: transparent;
}
.vitals-legend-dot.vitals-series-1 { border-color: var(--chart-1); background: var(--chart-1); }
.vitals-legend-dot.vitals-series-2 { border-color: var(--chart-2); background: var(--chart-2); }

/* --- Admin > EHR Import review cards --------------------------------
   A card per record rather than a table row. An EHR holds a dozen fields
   worth carrying per person and every one has to be editable before it is
   written; a table that wide scrolls sideways and nobody reads the
   right-hand half, which is where the address and the physician live. */
.import-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 16px;
  margin: 0 0 12px;
  background: var(--surface);
}
/* A record that cannot be written yet -- no name, no email -- is marked on
   its edge rather than hidden or disabled: the admin is about to type the
   missing field in, which is the whole reason it is listed. */
.import-card-blocked { border-left: 4px solid var(--state-warn); }
.import-card-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  cursor: pointer;
  margin-bottom: 10px;
}
.import-card-head input[type=checkbox] { flex: none; }
.import-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px 14px;
}
.import-grid > label {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 0.76rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 0;
}
.import-grid > label > input,
.import-grid > label > select {
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.9rem;
  color: var(--text);
  width: 100%;
}
.import-grid > label > .help-text { text-transform: none; letter-spacing: 0; margin: 2px 0 0; }
/* Address and Languages read as one field, not two half-width ones. Falls
   back to a single column on a phone with everything else. */
.import-grid-wide { grid-column: span 2; }
@media (max-width: 640px) {
  .import-grid { grid-template-columns: 1fr; }
  .import-grid-wide { grid-column: span 1; }
}
.import-card-extra { margin: 10px 0 0; }

/* --- EHR Import: the pull bar and the per-record panel ---------------- */
.ehr-pull-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 0 0 14px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-subtle);
}
/* One record at a time: twenty-odd editable fields per patient is a form,
   opened for the record being worked on and closed before the next. */
details.import-card > summary { cursor: pointer; list-style: none; }
details.import-card > summary::-webkit-details-marker { display: none; }
details.import-card > summary::before {
  content: "\25B8"; color: var(--muted); margin-right: 2px; display: inline-block;
  transition: transform 0.12s ease;
}
details.import-card[open] > summary::before { transform: rotate(90deg); }
details.import-card[open] { box-shadow: 0 1px 6px rgba(0, 0, 0, 0.07); }
/* Fields on the left, the old system's own record on the right. Roughly
   2:1 -- the source is for reading while filling the other side in, not
   for working in. */
.import-body { display: grid; grid-template-columns: 2fr 1fr; gap: 18px; margin-top: 12px; }
.import-group {
  margin: 16px 0 6px; font-size: 0.76rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted);
  border-bottom: 1px solid var(--border); padding-bottom: 4px;
}
.import-fields > .import-group:first-child { margin-top: 0; }
.import-source {
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface-subtle); padding: 10px 12px;
  font-size: 0.8rem; max-height: 560px; overflow-y: auto;
}
.import-source-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 8px; margin-bottom: 8px;
}
.import-source dl { margin: 0; }
.import-source dt {
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted); margin-top: 8px;
}
.import-source dd { margin: 1px 0 0; word-break: break-word; }
.import-source code { font-size: 0.72rem; white-space: pre-wrap; }
@media (max-width: 900px) {
  .import-body { grid-template-columns: 1fr; }
  .import-source { max-height: 320px; }
}

/* --- Manual claim submission pack ------------------------------------ */
.pack-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
.pack-dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; }
.pack-dl dt {
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted); align-self: baseline;
}
.pack-dl dd { margin: 0; word-break: break-word; }
