/* Design System for Accountability */

/* Fonts and Theme Tokens */
:root {
  --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --sidebar-width: 232px;

  /* Shared Colors */
  --compliant-rgb: 16, 185, 129; /* Emerald Green */
  --marginal-rgb: 245, 158, 11;  /* Amber Yellow */
  --non-compliant-rgb: 244, 63, 94; /* Rose Red */

  --color-success: rgb(var(--compliant-rgb));
  --color-warning: rgb(var(--marginal-rgb));
  --color-danger: rgb(var(--non-compliant-rgb));

  /* The fill of .btn-danger, and ONLY that. White on --color-danger (#f43f5e)
     is 3.7:1, under the 4.5:1 a 13-14px label needs. --color-danger itself
     stays: it and --non-compliant-rgb are the base hue behind the heatmap, 34
     tints and a scoring test, and darkening them would repaint all of those.
     #be123c is 6.3:1 with white, and still 5.5:1 at the 0.9 hover opacity. */
  --btn-danger-bg: #be123c;

  --transition-fast: 0.15s ease;
  --transition-normal: 0.25s ease;
  --transition-slow: 0.4s cubic-bezier(0.16, 1, 0.3, 1);

  /* Flat-clinical radii. Small, and only three of them: 4 for chips and inline
     badges, 6 for controls and rows, 8 for cards and panels.
     --border-radius-full survives for the two things that are genuinely round —
     the sidebar status dot and the settings switch knob. */
  --border-radius-sm: 4px;
  --border-radius-md: 6px;
  --border-radius-lg: 8px;
  /* The top corners of a phone bottom sheet (style guide §1). */
  --border-radius-sheet: 12px;
  --border-radius-full: 9999px;

  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);

  /* --shadow-md and --shadow-lg are declared PER THEME below, because they are
     no longer the same idea in both: a flat dark card casts nothing, while a
     white card on a near-white ground needs a 1px lift to have an edge at all.
     print.css re-declares both on `body.dark-theme, body.light-theme` — equal
     specificity, later source order — so print still wins either way. */

  /* Kept as a NAME rather than deleted. print.css neutralises it, and any
     var(--shadow-glow) that outlives this restyle must resolve to nothing
     rather than to an unset custom property. */
  --shadow-glow: none;

  /* Phase 11 A1: the heading type scale. Each level is at least 1.2x the one
     below it (24 / 20 = 1.2, 20 / 16 = 1.25, 16 / 13 = 1.23), and none is under
     11px. There is no element-level h1..h4 rule on purpose: each context rule
     (the page title, a dialog title, a section or card head, a card or form
     subhead) reads its level's size from here. --fs-h1 is the page title,
     --fs-h2 a dialog title, --fs-h3 a section or list-card head, --fs-h4 a
     card name, form subhead or empty-state title. */
  --fs-h1: 24px;
  --fs-h2: 20px;
  --fs-h3: 16px;
  --fs-h4: 13px;
  --fs-xs: 11px;

  /* Phase 11 A3: the ONE keyboard focus ring. Every :focus-visible rule in
     this file draws `outline: var(--focus-ring-width) solid
     var(--focus-ring-color)` at var(--focus-ring-offset), or at
     var(--focus-ring-inset) where an overflow-clipping parent would cut an
     outside ring. The colour is --color-brand, which only exists per theme, so
     --focus-ring-color is declared in each theme block below (a var() here on
     :root would resolve before body's theme is known). Measured contrast is in
     the step's report: at least 3:1 on --bg-card and --bg-app in both themes. */
  --focus-ring-width: 2px;
  --focus-ring-offset: 2px;
  --focus-ring-inset: -2px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   THE PALETTES — "flat clinical"
   ─────────────────────────────────────────────────────────────────────────────
   Opaque surface STEPS and 1px hairlines, not blur and glow. The three steps
   are --bg-app (the page) then --bg-sidebar (chrome) then --bg-card (content),
   with --bg-input one step BELOW the card so a field reads as a well rather
   than a raised tile. Nothing here is translucent: a translucent card takes its
   contrast from whatever happens to be behind it, which is why the old light
   theme had almost none.

   STATUS COLOUR COMES IN TWO GRADES and they are not interchangeable:
     - --color-success/-warning/-danger (from the *-rgb triples) are the BASE
       hues. They are for FILLS: progress bars, dots, the small status squares,
       where the colour sits on its own and carries the meaning by itself.
     - --text-success/-warning/-danger are the READABLE TINTS, for status as
       TEXT on a surface. The base rose on #181d24 is about 3:1; the tint is
       about 5:1. On white the relationship inverts, which is why the light
       values are darker rather than lighter.

   --color-brand-fg is the foreground ON a brand fill. It exists because
   .btn-primary used to hard-code #ffffff, which was fine on the old vivid cyan
   and is not on the toned one.
   ───────────────────────────────────────────────────────────────────────────── */

/* Dark Theme (Default) */
/* Native controls (checkbox and radio fill, date icons, pickers, autofill,
   scrollbars) follow color-scheme, not our tokens. The class lives on <body>, so
   the root is set through :has() as well. */
:root:has(body.dark-theme) { color-scheme: dark; }
:root:has(body.light-theme) { color-scheme: light; }
body.dark-theme {
  color-scheme: dark;
  --bg-app: #0f1216;
  --bg-sidebar: #14181e;
  --sidebar-text: #e8ecf1;
  --sidebar-text-muted: #98a2b3;
  --sidebar-border: #262d37;
  --sidebar-item-hover: #1b2129;

  --bg-card: #181d24;
  --bg-card-hover: #1b2129;
  --bg-input: #14181e;
  --bg-input-focus: #1e242c;
  --border-color: #2a313b;
  --border-color-light: #262d37;

  --text-primary: #e8ecf1;
  --text-secondary: #98a2b3;
  /* Raised from #6b7686 (3.7:1 on --bg-card) for the 11px footnotes and the
     placeholders: 5.1:1 on --bg-card, 5.4:1 on --bg-input, 4.7:1 on
     --bg-input-focus. Still a step below --text-secondary (6.6:1). Must not be
     print.css's #555555 (print.contract.test.js). */
  --text-muted: #838e9e;

  --text-success: #34d399;
  --text-warning: #f5b544;
  --text-danger: #fb7185;

  --color-brand: #3aa6cc; /* Toned teal-cyan */
  --color-brand-hover: #2f93b6;
  --color-brand-glow: rgba(58, 166, 204, 0.12); /* a TINT now, never a shadow */
  /* The same colour as bare channels, so a rule can choose its own alpha —
     the idiom --compliant-rgb and its siblings already use at the top of this
     file. The guide's spotlight fades a ring from opaque to nothing, which one
     fixed rgba() value cannot express. Keep in step with --color-brand above. */
  --color-brand-rgb: 58, 166, 204;
  --color-brand-fg: #0f1216;
  /* The focus ring (A3): the brand, 6.0:1 on --bg-card, 6.5:1 on --bg-app. */
  --focus-ring-color: var(--color-brand);

  --bg-overlay: rgba(6, 8, 11, 0.7);
  --glass-blur: none;
  --glass-border: 1px solid #262d37;

  --shadow-md: none;
  --shadow-lg: 0 20px 40px rgba(0, 0, 0, 0.4); /* modals only */
}

/* Light Theme */
body.light-theme {
  --bg-app: #eef1f5;
  --bg-sidebar: #ffffff;
  --sidebar-text: #1a222c;
  --sidebar-text-muted: #5b6675;
  --sidebar-border: #d5dbe3;
  --sidebar-item-hover: #f1f4f8;

  --bg-card: #ffffff;
  --bg-card-hover: #fafbfc;
  --bg-input: #ffffff;
  --bg-input-focus: #f6f8fa;
  --border-color: #cfd6df;
  --border-color-light: #e3e8ee;

  --text-primary: #1a222c;
  --text-secondary: #5b6675;
  /* Raised from #8a94a3 (3.1:1 on white): 5.2:1 on --bg-card and --bg-input,
     4.6:1 on --bg-app, still lighter than --text-secondary (5.8:1). */
  --text-muted: #646e7e;

  --text-success: #047857;
  --text-warning: #b45309;
  --text-danger: #be123c;

  --color-brand: #0e7490; /* Clinical Teal */
  --color-brand-hover: #0b5f75;
  --color-brand-glow: rgba(14, 116, 144, 0.10);
  /* Channels for --color-brand, per the note in the dark theme above. */
  --color-brand-rgb: 14, 116, 144;
  --color-brand-fg: #ffffff;
  /* The focus ring (A3): the brand, 5.3:1 on --bg-card, 4.7:1 on --bg-app. */
  --focus-ring-color: var(--color-brand);

  --bg-overlay: rgba(16, 24, 40, 0.55);
  --glass-blur: none;
  /* The card border is --sidebar-border (#d5dbe3), NOT --border-color-light.
     This one value is what gives light mode its definition: at
     --border-color-light a white card on #eef1f5 has no visible edge. */
  --glass-border: 1px solid #d5dbe3;

  --shadow-md: 0 1px 2px rgba(16, 24, 40, 0.04);
  --shadow-lg: 0 20px 40px rgba(16, 24, 40, 0.18);
}

/* Global Reset & Base Styling */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* Every checkbox and radio the app has NOT given a custom UI — the medication
   filters, the picker rows — was rendering as the operating system's default
   white box, which on the dark theme is the brightest thing on the screen and
   on the light one is the only unthemed control. One line, both themes. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--color-brand);
}

body {
  font-family: var(--font-family);
  background-color: var(--bg-app);
  color: var(--text-primary);
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  transition: background-color var(--transition-normal), color var(--transition-normal);
  -webkit-font-smoothing: antialiased;
}

/* ── Size tiers (style guide §1) ─────────────────────────────────────────────
   Three widths: desktop from 1200px, tablet 768-1199px (the sidebar is a 64px
   icon rail), phone under 768px (bottom bar, sheets). Below 1000px a table
   drops its secondary columns. LAYOUT follows the width:
     --row-h            a closed table row (auto on a phone: rows stack)
     --page-pad-x       its side padding alone (the status strip lines up with it)
     --page-pad         the view container's padding (a page with a bottom
                        bar gets 88px at the bottom on a phone)
   CONTROL SIZE follows the pointer, not the width: a mouse gets these sizes
   at every width, and a touch screen gets the 44px floor from the
   pointer-coarse block further down (a phone or a tablet is a touch screen).
   The style guide's 40px tablet controls become 44px that way.
     --control-h        primary and bordered buttons
     --control-row-h    a button inside a row
     --control-filter-h a segmented filter or a filter button
     --control-text-h   a text button */
body {
  --control-h: 32px;
  --control-row-h: 28px;
  --control-filter-h: 26px;
  --control-text-h: 24px;
  --row-h: 46px;
  --page-pad: 20px 28px 28px;
  --page-pad-x: 28px;
  --rail-width: 64px;
}

@media (max-width: 1199px) {
  body {
    --row-h: 56px;
    --page-pad: 16px 20px 24px;
    --page-pad-x: 20px;
  }
}

@media (max-width: 767px) {
  body {
    --row-h: auto;
    --page-pad: 12px 14px 24px;
    --page-pad-x: 14px;
  }
}

input, button, select, textarea {
  font-family: inherit;
  color: inherit;
}

/* ── The auth gate (LoginView) — M3-6 owns its real appearance ────────────────
   The M1-7 stopgap gave the bare inputs a readable surface so they were not
   white-on-white; M3-6 turns the whole gate into a coherent centered card. Every
   rule here is SCOPED to the [data-auth-screen] attribute LoginView sets on
   #auth-root, so none of it can reach the signed-in app behind the gate. It is
   token-based throughout, so dark and light both stay correct, and it is
   appearance only: the markup, the inputs and the submit handlers are
   LoginView's and unchanged. */

/* The centered card the gate mounts into (LoginView's shell()). */
.auth-card {
  /* 100% less a 16px gutter each side, so on a phone the card does not touch
     the screen edges. */
  width: calc(100% - 32px);
  max-width: 420px;
  margin: 8vh auto;
  padding: 28px;
}

/* Readable inputs — the surface the M1-7 note describes, now full-width and
   consistently spaced so the form reads as one column. */
[data-auth-screen] input {
  width: 100%;
  background-color: var(--bg-input);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  padding: 10px 12px;
  margin: 4px 0 16px;
  font-size: 14px;
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
}
[data-auth-screen] input:focus {
  background-color: var(--bg-input-focus);
  border-color: var(--color-brand);
  outline: none;
}

/* Password field with a show/hide eye. The wrapper takes over the input's own
   vertical margin so the absolutely-positioned button centres on the input box,
   and the input gains right padding so its text never runs under the eye. Rules
   sit AFTER [data-auth-screen] input so the equal-specificity overrides win. */
.password-field {
  position: relative;
  display: flex;
  align-items: center;
  margin: 4px 0 16px;
}
.password-field input {
  margin: 0;
  padding-right: 44px;
}
.password-toggle {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--border-radius-md);
  color: var(--text-secondary);
  cursor: pointer;
  transition: color var(--transition-fast);
}
.password-toggle:hover {
  color: var(--text-primary);
}
.password-toggle:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}
/* Default (aria-pressed="false") = password hidden → show the plain eye
   ("reveal"); pressed = visible → show the eye-with-slash ("hide"). */
.password-toggle .eye-off-icon { display: none; }
.password-toggle[aria-pressed="true"] .eye-icon { display: none; }
.password-toggle[aria-pressed="true"] .eye-off-icon { display: flex; }

/* The primary action fills the card width; .btn-primary already carries the
   brand colour, glow and hover, so this only sizes and centres it. */
[data-auth-screen] .btn-primary {
  width: 100%;
  justify-content: center;
  margin-top: 4px;
}

/* The "Set up your account" / "Sign in" affordance is a text link, not a button
   surface — but it earns a real focus ring for keyboard users. */
.auth-link-btn {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--color-brand);
  text-decoration: underline;
  cursor: pointer;
}
.auth-link-btn:hover {
  color: var(--color-brand-hover);
}
.auth-link-btn:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
  border-radius: 2px;
}

/* The signed-in footer control (boot.js renderSessionControls): the user's name
   and a styled Sign out laid along the sidebar-footer strip. */
#session-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  /* The row must never widen past the sidebar (2026-09-30, AdminLayoutIssue1):
     an account with no display name shows its email, one unbreakable word,
     and a flex item's minimum is its content, so the row grew to 292px in a
     207px column and Log out sat 85px outside the sidebar. The name shrinks
     and shortens with an ellipsis (its full text is in the span's title,
     src/js/boot.js); the pill and the button keep their size. */
  min-width: 0;
}
#session-controls [data-session-user] {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#session-controls .session-role-pill,
#session-controls .session-signout-btn {
  flex-shrink: 0;
}
.session-signout-btn {
  font-size: 11px;
  padding: 6px 12px;
  flex-shrink: 0;
}

button {
  cursor: pointer;
  border: none;
  background: none;
}

/* Scrollbar Customization */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: var(--border-color);
  border-radius: var(--border-radius-full);
}
::-webkit-scrollbar-thumb:hover {
  background: var(--text-secondary);
}

/* Application Layout Layout */
.app-layout {
  display: grid;
  grid-template-columns: var(--sidebar-width) 1fr;
  /* ONE ROW, AND IT MAY NOT GROW. This is the load-bearing line for keeping the
     sidebar's Sign out button on screen, and it is easy to think `height:
     100dvh` below already does the job. It does not.

     Without an explicit row, the two columns land in an IMPLICIT row, and
     implicit rows are `auto` — content-sized. The row therefore grew to the
     tallest item, which is the sidebar's content, and the grid overflowed its
     own 100dvh height: measured live at a 551px page height, the row resolved
     to 613px and the sidebar's footer rendered 50px past the bottom edge, with
     no scrollbar anywhere to reach it.

     `minmax(0, 1fr)` pins the row to the container's height and — the half that
     `1fr` alone would miss — gives it a zero MINIMUM. A bare `1fr` is
     `minmax(auto, 1fr)`, whose automatic minimum is its content, which is the
     same growth by another name. Exactly the trap recorded for `flex: 1 1 0` vs
     `flex: 1` in the roster section of this sheet, one layout module over. */
  grid-template-rows: minmax(0, 1fr);
  height: 100vh;
  height: 100dvh;
  width: 100vw;
}

/* Sidebar Navigation Styling */
/* THREE ROWS: header, menu, footer.

   The overflow that hid Sign out was NOT caused here — it was .app-layout's
   implicit auto row growing to this element's content, which is fixed at its
   own rule above. This structure is what decides WHICH part gives once the
   sidebar is correctly constrained: as three rows, the footer is laid out
   inside the box and the middle row absorbs the squeeze, rather than the footer
   being the tail of a column that runs off the end.

   The middle row is `minmax(0, 1fr)` and .sidebar-menu carries `min-height: 0`
   for the same reason spelled out on .app-layout: a track or a child that keeps
   its automatic content minimum cannot shrink, and something that cannot shrink
   pushes its siblings out.

   MEASURED on the shared web build at 1280x551 (a 1366x768 laptop, once the
   browser chrome and the taskbar are taken out), where the menu carries EIGHT
   items because Admin and Send Feedback are both revealed: the sidebar wanted
   613px in a 551px viewport and Sign out sat 50px below the fold.

   Constraining the rows alone would trade one bug for another — with the roomy
   spacing the menu then clips 23px, cutting Send Feedback in half — so the
   short-viewport block further down compacts it enough that nothing is cut off
   at the heights a laptop actually has. Both halves were measured separately;
   neither is sufficient alone. */
.app-sidebar {
  background-color: var(--bg-sidebar);
  color: var(--sidebar-text);
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  padding: 20px 12px;
  border-right: 1px solid var(--sidebar-border);
}

.sidebar-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 20px;
  padding: 0 2px;
}

/* The mark is a CSS square with a letter in it, not an icon. The glowing person
   glyph it replaces was the single most decorative thing in the chrome, and a
   product mark that is also a picture of a user is a small lie in an app whose
   users are staff and whose subjects are residents. No SVG, no asset. */
.logo-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border-radius: var(--border-radius-md);
  background-color: var(--color-brand);
  color: var(--color-brand-fg);
  font-size: 13px;
  font-weight: 800;
  line-height: 1;
}

.logo-text h2 {
  font-size: 14px;
  font-weight: 650;
  line-height: 1.2;
}

/* The facility name. Not uppercase: it is a proper noun somebody chose, and
   small caps make a real name read like a category label. */
.logo-text span {
  display: block;
  font-size: 11px;
  color: var(--sidebar-text-muted);
  font-weight: 500;
  line-height: 1.3;
  margin-top: 1px;
}

.sidebar-menu {
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* `flex: 1` used to stretch this; the parent's `1fr` row does that now. What
     is load-bearing here is min-height: 0 — without it this row inherits the
     automatic minimum of its content and grows the sidebar past its box again,
     which is the whole defect the row structure exists to prevent. */
  min-height: 0;
  /* AND THE MENU SCROLLS (phase 4 step 5b). min-height: 0 above stops the menu
     pushing the sidebar past its box; this is what happens to the rows that no
     longer fit inside it. Without it they are simply clipped, which is the same
     unreachable-destination bug as the old footer overflow, one element in.

     The three declarations that make this work are spread over three rules and
     every one of them is load-bearing: .app-layout's `grid-template-rows:
     minmax(0, 1fr)` with a dvh height, .app-sidebar's `auto minmax(0, 1fr)
     auto`, and this pair. Remove any one and the menu has an auto-sized track
     to grow into, there is nothing to overflow, and this line goes quiet
     without failing. test/meta/sidebar-nav.test.js pins the whole chain rather
     than this declaration alone, for exactly that reason.

     It REPLACES the arithmetic row budget that used to sit in that guard. The
     budget was a ceiling on how many destinations the menu could hold at the
     compact spacing, and phase 4's one Houses entry put it 14px over 331px.
     Tightening the rows further would have bought one more entry and thinned
     the mouse targets; scrolling costs nothing and ends the question. */
  overflow-y: auto;
  /* A menu that has reached its end must not then scroll the page behind it. */
  overscroll-behavior: contain;
}

.menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--border-radius-md);
  font-size: 13px;
  font-weight: 500;
  color: var(--sidebar-text-muted);
  text-align: left;
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.menu-item:hover {
  color: var(--sidebar-text);
  background-color: var(--sidebar-item-hover);
}

.menu-item.active {
  color: var(--text-primary);
  background-color: var(--color-brand-glow);
  box-shadow: inset 3px 0 0 var(--color-brand);
}

.menu-item.active .menu-icon {
  color: var(--color-brand);
}

/* ── The three menu GROUPS ───────────────────────────────────────────────────
   A flat list of eight was already at its limit (see the short-viewport block
   below, and test/meta/sidebar-nav.test.js). The destinations are now rendered
   from app.js's VIEWS registry into Residents / Transportation / Facility setup / Admin, each in its
   own `<div class="menu-group" role="group" aria-labelledby>` with a heading.

   The separator that used to ride #nav-settings — a border above the Settings
   button, hand-placed to split "places you are" from "things you do" — is gone:
   the headings do that job now, for every group rather than at one hand-picked
   seam, and an id-specific border would draw a second line in the middle of a
   labelled group. */
.menu-group {
  display: flex;
  flex-direction: column;
  gap: inherit;
}

/* A group with nothing visible in it (Admin, for a member of staff) is hidden
   whole, heading included — otherwise the gate would leave a label standing over
   an empty space. L-0009 again: .menu-group sets an explicit `display: flex`,
   which beats the UA [hidden] rule, so it must be restated. */
.menu-group[hidden] {
  display: none !important;
}

/* The heading. Small, muted and uppercase — it is a category label, not a
   destination, and it must not read as a disabled menu item. Not a <h_> in the
   markup: `role="group"` + aria-labelledby already gives it to assistive
   technology, and a heading level here would invent an outline the page's real
   headings do not have. */
.menu-group-heading {
  padding: 8px 10px 4px;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sidebar-text-muted);
  opacity: 0.75;
}

/* The first group sits directly under the facility name and needs no extra air
   above it; every group after the first does. */
.menu-group + .menu-group {
  margin-top: 6px;
}

/* ── An ACTION in the menu, not a destination ────────────────────────────────
   Send feedback lives in the menu because that is where people look for it, but
   it opens a modal rather than swapping the view — so it must not look like a
   seventh place you can BE. A rule above it separates the two kinds, and it
   never takes .active (nothing sets it: app.js returns early for a menu item
   with no data-view, before the active class is moved).

   It sits BELOW the rendered groups — app.js inserts them before it — and it is
   deliberately outside all three: it is not a place, so it belongs under no
   category heading. margin-top:auto is gone (it used to strand Feedback at the
   foot of the menu) and the separator it once carried is gone too; the group
   headings draw those divisions now. It keeps a padding-top of its own so it
   still breathes under the last group — and so the short-viewport block below
   still has a value to compact. */
.menu-item-action {
  padding-top: 8px;
  font-weight: 500;
}

.menu-item-action:hover {
  /* The separator would otherwise disappear under the hover fill. */
  background-color: transparent;
  color: var(--color-brand);
}

.menu-icon {
  flex-shrink: 0;
}

/* ── RESTATING [hidden], FOR EVERY GATED THING ───────────────────────────────
   .menu-item sets an explicit `display: flex`, which BEATS the user agent's
   `[hidden] { display: none }` rule — so setting the attribute hides nothing on
   its own. This was two ids when there were two gated controls; it is the CLASS
   now, because the role gate is applied from app.js's registry to whichever
   entries declare `roles`, and the next gated destination must not depend on
   somebody remembering to add its id here.

   The two ids are KEPT beside it, not as belt and braces but as the record of
   which controls the gate was built for: #nav-admin (admins, on the shared
   backend) and #feedback-btn (any authenticated user, shared backend — its
   route needs a server).

   `[data-admin-only]` is the in-view half, stamped by src/js/adminOnly.js on a
   control that only an admin may use. Same trap, any element: a .btn-secondary
   sets its own display too. The server re-checks the role on every route either
   way — this decides what is offered, never what is authorized. */
.menu-item[hidden],
#nav-admin[hidden],
#feedback-btn[hidden],
[data-admin-only][hidden] {
  display: none !important;
}

/* ── The signed-in role, as a pill ───────────────────────────────────────────
   src/js/boot.js renders it beside the user's name in the footer. Nothing showed
   the role before, which at a shared station is a question people ask out loud.
   It is a LABEL: the server re-checks the role on every route. */
.session-role-pill {
  align-self: center;
  padding: 1px 7px;
  border: 1px solid var(--sidebar-border);
  border-radius: var(--border-radius-full);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sidebar-text-muted);
}


.sidebar-footer {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0; /* a grid item's minimum is its content; the footer must not widen the sidebar */
  padding-top: 16px;
  border-top: 1px solid var(--sidebar-border);
}

/* A plain row, not a badge. The box around it made a passive status read like a
   control. The padding stays (trimmed) because the short-viewport block below
   compacts it, and a rule with nothing to compact is a rule that silently
   stops meaning anything. */
.backup-status-widget {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--sidebar-text-muted);
  padding: 6px 0;
}

.status-indicator {
  width: 7px;
  height: 7px;
  flex-shrink: 0;
  border-radius: var(--border-radius-full);
}

.status-indicator.warning {
  background-color: var(--color-warning);
}

.status-indicator.success {
  background-color: var(--color-success);
}

/* NOTE ON PLACEMENT: this block sits BELOW every rule it overrides
   (.app-sidebar, .sidebar-header, .sidebar-menu, .menu-item, .menu-item-action,
   .sidebar-footer, .backup-status-widget) and must stay there. A media query
   adds no specificity, so a compaction rule placed above the plain rule it
   means to override simply loses to it — silently, and only for the selectors
   that happen to be declared further down. That is not hypothetical: the first
   version of this block was placed higher up, where .sidebar-footer and
   .backup-status-widget are declared later, and both of those overrides were
   dead on arrival. */
/* ── A shorter viewport gets a shorter sidebar ───────────────────────────────
   WHERE 700px COMES FROM. Measured: at the default spacing, eight menu items
   plus the header and the footer occupy 590px. Add headroom for a footer that
   is taller than the one measured — a long signed-in email wraps to a second
   line — and the default stops fitting somewhere under ~660px. 700px is the
   round number above that, so the compaction switches on just before it is
   needed and never while the roomy spacing still fits.

   It is deliberately NOT set at the 551px this was reported from. A threshold
   tuned to one laptop leaves every slightly shorter window broken, and the
   compaction has to be in force BEFORE the sidebar runs out of room, not at the
   exact height where it already has.

   WHAT IT APPLIES TO. Any viewport at least 901px wide and at most 700px tall —
   which includes a short Electron window on the desktop build, not only the
   browser. That is intended: both are mouse-driven and both benefit. The
   desktop build shows six menu items rather than eight (Admin and Send Feedback
   need the server and stay hidden), so it was never over its box; it simply
   gets the tighter spacing too.

   WHAT IT DOES NOT APPLY TO. Anything under 901px wide — the off-canvas drawer.
   Phones are narrow but TALL, so they have this space to spare, and their
   targets must stay at the 44px floor the M3 mobile rows settled on. A
   height-only query would have compacted a phone's menu to 36px rows to solve a
   laptop's problem, so the width clause is load-bearing rather than decorative.

   Nothing here is a new mechanism: every value is the existing one, smaller. */
@media (min-width: 1200px) and (max-height: 700px) {
  .app-sidebar {
    padding: 10px 12px;
  }

  .sidebar-header {
    margin-bottom: 12px;
  }

  .sidebar-menu {
    gap: 1px;
  }

  /* ~28px rows rather than ~33px. This is the mouse-driven layout — the touch
     floor is enforced on the drawer, which this query excludes. Taken from 5px
     to 4px when phase 5 added Work Today, the ninth row. */
  .menu-item {
    padding: 4px 10px;
  }

  .menu-item-action {
    padding-top: 4px;
  }

  /* The four group headings are the height the groups cost over a flat list,
     so they are compacted with everything else. ~12px rather than ~22px.
     test/meta/sidebar-nav.test.js reads these numbers back out and spends them
     against the 551px budget. */
  .menu-group-heading {
    padding: 4px 10px 2px;
  }

  .menu-group + .menu-group {
    margin-top: 4px;
  }

  .sidebar-footer {
    gap: 8px;
    padding-top: 8px;
  }

  .backup-status-widget {
    padding: 4px 0;
  }
}

.theme-toggle-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px;
  background-color: var(--sidebar-item-hover);
  border: 1px solid var(--sidebar-border);
  border-radius: var(--border-radius-md);
  color: var(--sidebar-text);
  font-size: 13px;
  font-weight: 550;
  transition: all var(--transition-fast);
}

.theme-toggle-btn:hover {
  background-color: var(--sidebar-item-hover);
  color: #ffffff;
}

/* Main Content Workspace Layout */
.app-main {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

/* Header Styling */
.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Style guide §4.1: 14px 28px and at least 66px tall, so the header is the
     same height on every view whether or not it carries the week. */
  padding: 14px 28px;
  min-height: 66px;
  border-bottom: 1px solid var(--border-color-light);
  /* The header is CHROME, so it takes the chrome surface and reads as one
     continuous band with the sidebar rather than as a very wide card. */
  background: var(--bg-sidebar);
  z-index: 10;
}

/* Tablet: the page's side padding is 20px (style guide §1). */
@media (max-width: 1199px) {
  .app-header {
    padding-left: 20px;
    padding-right: 20px;
  }
}

/* Mobile sidebar drawer (M3-1) — desktop-inert half.
   Both the hamburger and the drawer backdrop are display:none at >=900px, so
   the grid renders the fixed 260px sidebar exactly as before and no hamburger
   shows. Everything that reshapes the layout lives in the @media block below. */
.sidebar-toggle {
  display: none;            /* shown only below the 900px breakpoint */
  align-items: center;
  justify-content: center;
  width: 44px;              /* the >=44x44 touch target the a11y guideline wants */
  height: 44px;
  flex-shrink: 0;
  margin-right: 4px;
  border-radius: var(--border-radius-md);
  color: var(--text-secondary);
  transition: all var(--transition-fast);
}

.sidebar-toggle:hover {
  background-color: var(--bg-input-focus);
  color: var(--text-primary);
}

.sidebar-backdrop {
  display: none;            /* only an open drawer, on a phone, reveals it */
}

/* Back (v1.52), left of the title. The header is space-between, so with a
   third item the title would float to the middle; the title's auto right
   margin takes the free space instead and keeps it beside the button. Drawn
   like the header's icon buttons, with the word beside the chevron. */
.back-btn {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 34px;
  padding: 0 12px 0 6px;
  margin-right: 14px;
  flex-shrink: 0;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  background-color: var(--bg-card);
  color: var(--text-secondary);
  font-weight: 600;
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.back-btn:hover {
  background-color: var(--bg-card-hover);
  color: var(--text-primary);
}

/* .back-btn sets display, which beats the UA [hidden] rule. */
.back-btn[hidden] {
  display: none;
}

.header-title-section {
  margin-right: auto;
}

.header-title-section h1 {
  font-size: var(--fs-h1);
  font-weight: 650;
  letter-spacing: -0.2px;
}

.header-title-section p {
  font-size: 12px;
  margin-top: 2px;
}

.muted {
  color: var(--text-secondary);
}

/* The sentence under a form's fields in Transportation and vehicle records: the
   standard small text (13px, like the labels), not the dialog's 16px body (G7-018). */
.muted.form-footnote {
  font-size: var(--fs-h4);
}

/* THE DATE CONTROL (Phase 11 X1a). One look for the three dates a page can
   show: the header's week, Work today's day and the Transport Runs tab's day.
   Each keeps its own state and handler; only the pill is shared. The header
   uses .week-navigator as it always has; Work today wraps its arrows and day
   label in it, and the Transport Runs tab wraps its date input in it. The
   font-size is set HERE, on the pill, so the three measure the same whatever
   is inside them. The 44px floor under a touch pointer is in the
   (pointer: coarse) block, beside the arrows' own. */
.week-navigator {
  display: flex;
  align-items: stretch;
  height: 34px;
  font-size: 13px;
  background-color: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  overflow: hidden;
}

.week-navigator[hidden] {
  display: none;
}

/* 32x32 at desktop. The 44x44 touch floor for these two arrows lives in the
   max-width:900px block further down and is untouched by this rule. */
.nav-arrow-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  flex-shrink: 0;
  color: var(--text-secondary);
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

/* Hairlines BETWEEN the three parts rather than a border around each: the
   navigator is one control, not three buttons that happen to be adjacent.
   Class selectors, not the header's ids, so Work today's day arrows draw the
   same two lines. */
.week-navigator > .nav-arrow-btn:first-child {
  border-right: 1px solid var(--border-color);
}

.week-navigator > .nav-arrow-btn:last-child {
  border-left: 1px solid var(--border-color);
}

.nav-arrow-btn:hover {
  background-color: var(--bg-input-focus);
  color: var(--text-primary);
}

/* Right-side header control cluster: week navigator + theme toggle. */
.header-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* The view's own header actions (Phase 11 F3): a slot in .header-actions,
   before the guide button, that renderActiveView empties before every mount.
   Hidden while EMPTY, because an empty flex item still takes the cluster's
   12px gap (and, below 900px, the header's column-gap), which would shift the
   guide button left on every view that puts nothing here. The [hidden]
   restatement is the usual one: this rule sets its own display, which beats
   the UA [hidden] rule. */
.view-header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.view-header-actions:empty,
.view-header-actions[hidden] {
  display: none;
}

/* Icon-only header button (theme toggle). Sized to sit level with the week
   navigator pill; the sun/moon swap below picks which glyph shows. */
.header-icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  background-color: var(--bg-card);
  color: var(--text-secondary);
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.header-icon-btn:hover {
  background-color: var(--bg-card-hover);
  color: var(--text-primary);
}

/* Show the sun in dark mode (click to brighten → light), the moon in light
   mode (click → dark). Default markup order shows the sun; the light theme
   class swaps to the moon. */
#theme-toggle .moon-icon { display: none; }
#theme-toggle .sun-icon { display: block; }
body.light-theme #theme-toggle .sun-icon { display: none; }
body.light-theme #theme-toggle .moon-icon { display: block; }

.week-picker-trigger {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 14px;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  user-select: none;
  min-width: 180px;
  justify-content: center;
}

/* The ISO week number, muted, after the date range. Two people comparing a
   client-week across a phone call say "W36", not "the week of August 31st". */
.week-iso-suffix {
  color: var(--text-muted);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.hidden-week-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

/* Views Wrapper */
.view-content {
  flex: 1;
  /* Per size tier (style guide §1): 20/28/28 desktop, 16/20/24 tablet,
     12/14/24 phone (88 at the bottom when the page has a bottom bar). */
  padding: var(--page-pad);
  overflow-y: auto;
  /* Keep the scrollbar's lane whether or not the page scrolls, so the header
     buttons do not slide 8px sideways between a long tab and a short one. */
  scrollbar-gutter: stable;
  position: relative;
}

/* Card Elements & Glassmorphism */
/* THE HUB.
   The class name is historical: there is no glass left in it. Every card in
   every view is .glass-card plus a role class, so this one rule is where "flat"
   is actually decided — an opaque surface, one hairline, an 8px radius, and a
   shadow that is none on dark and a 1px lift on light. It keeps the name
   because renaming it would touch every view for nothing a user can see. */
.glass-card {
  background: var(--bg-card);
  border: var(--glass-border);
  border-radius: var(--border-radius-lg);
  padding: 16px;
  box-shadow: var(--shadow-md);
  /* No transform here any more: the lift-on-hover went with the glow. A card
     that moves under the pointer is a marketing affordance, and this is a
     surface somebody reads a medication count off. */
  transition: box-shadow var(--transition-fast), background-color var(--transition-fast);
}

/* Grid Layouts */
/* Four equal columns, and EQUAL is the point: auto-fit/minmax reflowed to three
   or five depending on how wide the window happened to be, so the same four
   numbers landed in a different arrangement every time somebody resized. */
.grid-stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 24px;
}

/* Two columns on a phone rather than four. Four 90px tiles would put the status
   split's three swatch-and-count pairs into a horizontal scroll, and
   test/e2e/geometry.test.js measures that the page never scrolls sideways at
   390px. A media query adds no specificity, so this must stay BELOW the rule it
   overrides. */
@media (max-width: 999px) {
  .grid-stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* LABEL ABOVE VALUE. The old tile led with a 24px number and explained it
   underneath, which reads fine when you already know what the tile is and not
   at all when you are scanning four of them. .stat-info keeps its place in the
   markup for the views that use the tile; the ordering is now the markup's. */
.stat-card {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 14px 16px;
}

.stat-info {
  display: flex;
  flex-direction: column;
}

.stat-value {
  font-size: 26px;
  font-weight: 650;
  line-height: 1.1;
  letter-spacing: -0.5px;
  margin-top: 6px;
  /* Lining figures, so a column of counts stays a column. */
  font-variant-numeric: tabular-nums;
}

.stat-label {
  font-size: 11px;
  color: var(--text-secondary);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}

/* Status tint for a tile's number. The BASE hue is for fills; a 26px numeral is
   text, so it takes the readable tint. */
.stat-value.is-compliant { color: var(--text-success); }
.stat-value.is-marginal { color: var(--text-warning); }
.stat-value.is-non-compliant { color: var(--text-danger); }

/* Section Header */
.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

/* ── Segmented control ───────────────────────────────────────────────────────
   One bordered box with two halves, both visible. It replaced a single button
   labelled "Switch to List View", which is a control that describes what will
   happen rather than what is true — you had to read the button to find out
   which view you were looking at.

   Its handler PICKS rather than flips (see DashboardView), so the halves are
   real options and not two labels on one toggle. The active half is filled with
   the hairline colour rather than the brand: this chooses a layout, and a brand
   fill here would make it compete with the primary action on the same row. */
.segmented {
  /* Style guide §3: an inset well with the options as 4px-radius pills in it,
     the selected one raised onto --bg-input-focus. */
  display: inline-flex;
  align-items: stretch;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  background-color: var(--bg-input);
  overflow: hidden;
}

.segmented-option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: calc(var(--control-filter-h) - 6px);
  padding: 0 10px;
  border-radius: var(--border-radius-sm);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  color: var(--text-secondary);
  background-color: transparent;
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.segmented-option:hover {
  color: var(--text-primary);
}

.segmented-option[aria-pressed="true"] {
  background-color: var(--bg-input-focus);
  font-weight: 600;
  color: var(--text-primary);
}

/* ══════════════════════════════════════════════════════
   PAGE ANATOMY (style guide §4). One order on every view:
     header (the shell's .app-header)
     .view-summary          counts, chips for what needs action, a queue
                            button, and at the right .view-summary-end with
                            the page utilities and the one primary
     .tabs-underline        only for a view with sub-views
     one .glass-card:
       .card-toolbar        search, a .segmented filter, dropdown filters
       .card-colhead        11px uppercase column names
       .table-row           the rows; the card sets --cols once and the head
                            and every row share it
     .view-footnote         only when a rule needs stating
   An open row is .table-row-open: the brand border, then a .row-actionbar
   (one primary, up to two bordered, More at the right), a .decision-strip
   when something waits on a yes or no, and .detail-sections.
   ══════════════════════════════════════════════════════ */
.view-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin: 0 0 14px;
  font-size: 13px;
  color: var(--text-secondary);
}

.view-summary strong {
  font-weight: 600;
  color: var(--text-primary);
}

.view-summary > :first-child strong,
.view-summary > strong:first-child {
  font-size: 15px;
}

.view-summary-end {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.card-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border-color-light);
}

.card-colhead,
.table-row {
  display: grid;
  grid-template-columns: var(--cols, minmax(0, 1fr) auto);
  align-items: center;
  gap: 12px;
  padding: 0 14px;
  border-bottom: 1px solid var(--border-color-light);
}

.card-colhead {
  min-height: 32px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.table-row {
  min-height: var(--row-h);
  font-size: 13px;
  transition: background-color var(--transition-fast);
}

/* Only where a pointer can hover: a touch screen would leave the last row
   it touched tinted. */
@media (hover: hover) {
  .table-row:hover {
    background-color: var(--bg-card-hover);
  }
}

.table-row:last-child {
  border-bottom: none;
}

/* Each cell may shrink: a long name ellipses inside its track instead of
   widening the grid. */
.table-row > * {
  min-width: 0;
}

.table-row-name {
  overflow: hidden;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--text-primary);
}

.table-row-facts {
  font-size: 12px;
  color: var(--text-secondary);
}

.table-row-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
}

.table-row-open {
  margin: 6px 0;
  border: 1px solid var(--color-brand);
  border-radius: var(--border-radius-lg);
  box-shadow: 0 0 0 3px var(--color-brand-glow);
}

.table-row-open:hover {
  background-color: transparent;
}

.row-actionbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 0 16px 14px;
}

.row-actionbar-more {
  margin-left: auto;
}

.decision-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  font-size: 13px;
  background-color: rgba(var(--marginal-rgb), 0.07);
}

.decision-strip-actions {
  display: flex;
  gap: 8px;
  margin-left: auto;
}

.detail-sections {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  border-top: 1px solid var(--border-color-light);
}

.detail-section {
  padding: 14px 16px;
}

.detail-section h5 {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.card-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 28px 14px;
  font-size: 13px;
  text-align: center;
  color: var(--text-secondary);
}

.view-footnote {
  /* A readable measure at 1920px, not a 1600px line. */
  max-width: 75ch;
  margin: 10px 0 0;
  font-size: 11px;
  color: var(--text-muted);
}

/* A phone stacks a row (style guide §5): the name and its status chip on the
   first line, the facts line under them across the row. The column head goes:
   a stacked row has no columns to name. */
@media (max-width: 767px) {
  .card-colhead {
    display: none;
  }

  .table-row {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 4px 10px;
    padding: 12px 14px;
  }

  .table-row-name {
    font-size: 15px;
  }

  .table-row-facts {
    grid-column: 1 / -1;
    font-size: 13px;
  }
}

/* [hidden] restated: every class above sets its own display. */
.view-summary[hidden],
.view-summary-end[hidden],
.card-toolbar[hidden],
.card-colhead[hidden],
.table-row[hidden],
.table-row-actions[hidden],
.row-actionbar[hidden],
.decision-strip[hidden],
.decision-strip-actions[hidden],
.detail-sections[hidden],
.card-empty[hidden] {
  display: none !important;
}

.section-header h3 {
  font-size: var(--fs-h3);
  font-weight: 650;
}

.btn-primary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background-color: var(--color-brand);
  color: var(--color-brand-fg);
  padding: 8px 12px;
  border-radius: var(--border-radius-md);
  font-size: 13px;
  font-weight: 600;
  transition: background-color var(--transition-fast);
}
/* No translateY on hover. A button that jumps a pixel under the pointer is a
   marketing flourish; the colour step is the whole feedback now. */
.btn-primary:hover {
  background-color: var(--color-brand-hover);
}

.btn-secondary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background-color: var(--bg-card);
  border: 1px solid var(--border-color);
  color: var(--text-primary);
  padding: 7px 12px;
  border-radius: var(--border-radius-md);
  font-size: 13px;
  font-weight: 500;
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
}
.btn-secondary:hover {
  background-color: var(--bg-card-hover);
  border-color: var(--text-muted);
}

.btn-danger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background-color: var(--btn-danger-bg);
  color: #ffffff;
  padding: 8px 12px;
  border-radius: var(--border-radius-md);
  font-size: 13px;
  font-weight: 600;
  transition: all var(--transition-fast);
}
.btn-danger:hover {
  opacity: 0.9;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE DISABLED STATE, WHICH DID NOT EXIST.

   Found by the live pass of 2026-09-18: Settings' Import button is `disabled`
   on the hosted app (a restore would replace every record for everyone), and
   it computed `opacity: 1`, `cursor: pointer`, and the SAME colour, background
   and border as the enabled Export button beside it. The whole sheet had two
   `:disabled` rules — `.med-day-open` and one scoped to `.meeting-form` —
   and neither covered the three button classes everything else uses.

   `cursor: pointer` is the part that was actively wrong rather than merely
   missing: it is an assertion that the control can be clicked, made by the
   stylesheet, about a control the markup had already switched off. The
   `aria-describedby` on that button is correct, so a screen-reader user was
   told why and a sighted user was told the opposite.

   The other button this matters most for is Activate on the resident-notice
   card, which is disabled until a version has been saved. A live-looking
   Activate is how somebody concludes the notice system is broken.

   AFTER the :hover rules on purpose. `:disabled` and `:hover` are both one
   class plus one pseudo-class, so specificity does not separate them and the
   later rule wins; a disabled button still matches :hover in some engines.
   ══════════════════════════════════════════════════════════════════════════ */
.btn-primary:disabled,
.btn-secondary:disabled,
.btn-danger:disabled,
.btn-icon:disabled,
.btn-icon-label:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* The hover feedback has to go with it, or the control still answers the
   pointer as though it were live. Each one restates ITS OWN base value rather
   than sharing an `inherit`: `inherit` would take the PARENT's background,
   which for .btn-secondary is the card behind it and for .btn-primary is not
   the brand colour at all — a disabled button that changes colour on hover is
   the bug this rule exists to remove, arriving by a different road. */
.btn-primary:disabled:hover {
  background-color: var(--color-brand);
}

.btn-secondary:disabled:hover {
  background-color: var(--bg-card);
  border-color: var(--border-color);
}

.btn-danger:disabled:hover {
  opacity: 0.45;
}

.btn-icon:disabled:hover,
.btn-icon-label:disabled:hover {
  background: none;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE SHARED VOCABULARY (Phase 11 F1). Opt-in: nothing uses these classes
   until a screen adopts them, so adding them changes no existing screen.
   Values are the transportation handoff's, in this sheet's own tokens.

   Rules every class here keeps:
   - A class that sets `display` restates `[hidden] { display: none }` beside
     it. An author display rule beats the UA's [hidden] rule, so without the
     restatement `el.hidden = true` would leave the element on screen.
   - Sizes are SINGLE classes (.btn-sm, never .btn-primary.btn-sm). A compound
     selector is 0,2,0 and would beat the 44px floor in the (pointer: coarse)
     block, which is 0,1,0. The floor for .btn-sm, .btn-row and the underline
     tab is in that block, which sits below this one.
   - Overrides sit BELOW what they override: a media query adds no specificity.
   - The dialog footer is .modal-footer (hairline above, Cancel then the
     primary); there is deliberately no second .dialog-footer class.
   ══════════════════════════════════════════════════════════════════════════ */

/* Status chip: one class, a tinted fill, no outline. Radius 4, 11px/600.
   Tints are rgba(var(--*-rgb), .12), the form the rest of this sheet uses.
   Text on its tint, composited over --bg-card, is at least 4.5:1 in both
   themes (the lowest is brand on light, 4.5:1). */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: var(--border-radius-sm);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}

.chip[hidden] {
  display: none;
}

/* A phone reads chips a size up (style guide §3). */
@media (max-width: 767px) {
  .chip {
    padding: 2px 8px;
    font-size: 12px;
  }
}

.chip-neutral {
  color: var(--text-secondary);
  background-color: var(--bg-input-focus);
}

.chip-brand {
  color: var(--color-brand);
  background-color: rgba(var(--color-brand-rgb), 0.12);
}

.chip-success {
  color: var(--text-success);
  background-color: rgba(var(--compliant-rgb), 0.12);
}

.chip-warning {
  color: var(--text-warning);
  background-color: rgba(var(--marginal-rgb), 0.12);
}

.chip-danger {
  color: var(--text-danger);
  background-color: rgba(var(--non-compliant-rgb), 0.12);
}

.chip-muted {
  color: var(--text-muted);
  background-color: transparent;
}

/* Button sizes, for .btn-primary and .btn-secondary. Below both base rules so
   they win on source order at equal specificity. min-height rather than
   height, so a label that wraps grows the button instead of spilling out of
   it, and so the coarse-pointer floor (also min-height, and later) replaces
   it on a touch screen. */
.btn-sm {
  min-height: var(--control-row-h);
  padding: 0 10px;
  font-size: 12px;
}

/* A text button (style guide §3): no border, a tint on hover. Brand for a
   link, quiet in secondary, destructive in danger. */
.btn-text {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: var(--control-text-h);
  padding: 0 6px;
  border-radius: var(--border-radius-md);
  background-color: transparent;
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  color: var(--color-brand);
  transition: background-color var(--transition-fast);
}

.btn-text:hover {
  background-color: var(--bg-input-focus);
}

.btn-text.is-quiet {
  color: var(--text-secondary);
}

.btn-text.is-danger {
  color: var(--text-danger);
}

.btn-text[hidden] {
  display: none !important;
}

/* A row action on an admin list: 26px, 11px text. */
.btn-row {
  min-height: 26px;
  padding: 0 8px;
  font-size: 11px;
}

/* Underline tabs. The chosen tab is aria-selected="true" (or .is-active) and
   carries the brand underline as an inset shadow. An INSET underline rather
   than a border-bottom, so the rule the tabs sit on stays one unbroken hairline
   and the chosen marker is drawn on top of it. The label goes to the primary
   text colour: brand-coloured tab text made the whole strip read as links to
   somewhere else. The roster, Medications and log-window strips (tabs-header /
   tab-btn, modal-tabs / modal-tab-btn) use this rule too (Phase 11 R1a). */
.tabs-underline {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border-color-light);
  overflow-x: auto;
}

.tabs-underline[hidden] {
  display: none;
}

.tabs-underline .tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 12px;
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  color: var(--text-secondary);
  transition: color var(--transition-fast), box-shadow var(--transition-fast);
}

.tabs-underline .tab[hidden] {
  display: none;
}

.tabs-underline .tab:hover {
  color: var(--text-primary);
}

.tabs-underline .tab[aria-selected="true"],
.tabs-underline .tab.is-active {
  color: var(--text-primary);
  box-shadow: inset 0 -2px 0 var(--color-brand);
}

/* The count on a tab (a danger chip, radius 4). 11px rather than the handoff's 10px:
   nothing in the app is set below 11px. */
.tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 1px 6px;
  /* A count is a chip (style guide §3): radius 4, not a pill. */
  border-radius: var(--border-radius-sm);
  font-size: 11px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--text-danger);
  background-color: rgba(var(--non-compliant-rgb), 0.12);
}

.tab-count[hidden] {
  display: none;
}

/* Lists: one card with no padding, rows as a grid, hairlines between rows.
   Use as `glass-card list-card`; this rule sits below .glass-card so its
   padding wins. */
.list-card {
  padding: 0;
  overflow: hidden;
}

.list-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border-color-light);
  font-size: 13px;
  font-weight: 650;
}

.list-card-head[hidden] {
  display: none;
}

.list-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 6px 10px;
  min-width: 0;
  min-height: 44px;
  padding: 8px 16px;
  border-bottom: 1px solid var(--border-color-light);
  font-size: 13px;
  /* .list-card clips (overflow: hidden), so a long unbroken word (a note, a
     station) must break inside its cell rather than run under the card edge.
     Inherited by every cell; `anywhere` also lowers each cell's min-content
     width, so a minmax(0, 1fr) column wraps it instead of growing. */
  overflow-wrap: anywhere;
}

.list-row:last-child {
  border-bottom: none;
}

.list-row[hidden] {
  display: none;
}

/* The actions cell of a row: right-aligned, 6px apart. */
.list-row-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
  /* A button label never breaks mid-word (the row's `anywhere` is for text). */
  overflow-wrap: normal;
}

.list-row-actions[hidden] {
  display: none;
}

/* The Today home (Phase 11 X3a): panels are list cards in a grid that
   folds to one column at phone width; the three link cards sit below.
   v1.53: the cards in a row are one height (the panels' rows stretch), each
   card's button sits on its bottom edge and an empty state is centred in the
   space; TodayView.js caps each list at TODAY_ROW_CAP rows so no card is a
   column of its own (the operator's report: holes beside short cards). */
.today-home {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.today-home[hidden] {
  display: none;
}

.today-panels,
.today-links {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
  gap: 16px;
  align-items: start;
}

.today-panels[hidden],
.today-links[hidden] {
  display: none;
}

/* From a tablet up the panels are one block with no holes in it: Medications
   fills the left column across two rows, Pending meetings and Refills are the
   two rows beside it, and Supplies not issued runs the full width below. The
   rows stretch, so a short card is as tall as the space it shares (its button
   stays on the bottom edge, an empty state is centred) instead of leaving a
   gap under it. This supersedes G2-107, where each panel kept its own height
   and the medications panel left a blank column beside the short ones. */
.today-panels {
  align-items: stretch;
}

@media (min-width: 768px) {
  .today-panels {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .today-panels > [data-today-panel="meds"] {
    grid-row: span 2;
  }

  .today-panels > [data-today-panel="supplies"] {
    grid-column: 1 / -1;
  }

  /* The three link cards are one strip of shortcuts: a title and its button on
     one line, not a header over an otherwise empty card. */
  .today-links {
    display: flex;
    flex-wrap: wrap;
  }

  .today-link-card {
    flex: 1 1 260px;
  }
}

.today-link-card .today-panel-title {
  font-size: 13px;
  white-space: nowrap;
}

.today-link-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.today-link-card .list-card-head {
  flex: 1;
  min-width: 0;
  border-bottom: 0;
}

.today-link-card .today-panel-foot {
  flex: none;
  min-height: 0;
  border-bottom: 0;
}

.today-panel {
  display: flex;
  flex-direction: column;
}

.today-panel > .today-panel-foot {
  margin-top: auto;
}

.today-panel > .empty-state-container {
  flex: 1;
}

/* The "+ N more" line under a capped list. */
.today-more {
  color: var(--text-secondary);
  font-size: 12px;
  min-height: 0;
}

/* A pass on Today is one line: time, totals and the chips that are not zero,
   beside Open pass. The Medications screen keeps the three-line entry. The
   zero chips stay in the markup (their data-count is read by tests and the
   guide), only hidden; the [hidden]-style restatement is not needed, nothing
   here toggles them. */
.today-home .med-pass-entry > div:first-child {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
}

.today-home .med-pass-entry .list-row-chips {
  margin-top: 0;
}

.today-home .med-pass-entry [data-pass-entry-count][data-count="0"] {
  display: none;
}

.today-link-card[hidden] {
  display: none;
}

.today-panel-title {
  margin: 0;
  font-size: var(--fs-h3);
  font-weight: 650;
}

.today-panel-date {
  margin-left: auto;
  color: var(--text-secondary);
  font-weight: 500;
}

.today-row-name {
  font-weight: 600;
}

.today-row-detail {
  color: var(--text-secondary);
  font-size: 12px;
}

.today-pile-head {
  min-height: 0;
  color: var(--text-secondary);
  font-size: 12px;
  font-weight: 650;
}

/* Column templates, one modifier per list (the handoff's). The phone fallback
   is below, after all of them. */
.list-row-assignments,
.list-row-drivers {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) auto;
}

.list-row-standing {
  grid-template-columns: 80px minmax(0, 1.4fr) minmax(0, 1fr) auto;
}

.list-row-vehicles {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) minmax(0, 1.4fr) auto;
}

.list-row-places {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr) minmax(0, 0.8fr) auto;
}

.list-row-history {
  grid-template-columns: 150px minmax(0, 1fr) 110px auto;
}

/* A voided fill-up or service entry reads as voided at a glance: the date is
   struck and the details go to the secondary text colour (5.8:1 on a card, so
   the row stays readable). The Voided chip keeps its own text, unstruck. */
.list-row-history.is-voided > :first-child {
  text-decoration: line-through;
  color: var(--text-secondary);
}

.list-row-history.is-voided > :nth-child(2) {
  color: var(--text-secondary);
}

/* The Vehicle Records rows (VehicleRecords.js): a row's chips wrap inside their
   cell, and a Voided chip may carry a long note, so it wraps too. Restated
   [hidden] is not needed: nothing here hides. */
.list-row-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.vehicle-records .list-row .chip {
  white-space: normal;
}

/* The Records sections (Due, Service rules, Fill-ups, Service, Issues): the
   global reset zeroes heading margins, so each heading sat tight against the
   list above it (walkthrough 2026-10-04). */
.vehicle-records h3 {
  margin: 24px 0 8px;
  /* G7-029: a section head is --fs-h3, not the browser's 1.17em next to the
     20px dialog title. */
  font-size: var(--fs-h3);
}

.vehicle-records h3:first-child {
  margin-top: 0;
}

/* The add button under each Records list sat flush against the list's last
   row (walkthrough 2026-10-05). */
.vehicle-records .form-actions {
  margin-top: 8px;
}

/* The Today home's dose chips hold a medication name, a time and the status in
   one chip; a long name wraps inside the card instead of cutting the status
   word off. min-width 0 lets the flex item shrink below its content. */
.today-home .list-row-chips .chip {
  white-space: normal;
  overflow-wrap: anywhere;
  min-width: 0;
  max-width: 100%;
}

/* Neutral helpers for new layouts, in place of the borrowed rent-* and
   roster-item-* names. */
.card-section {
  padding-top: 10px;
  border-top: 1px solid var(--border-color-light);
}

.form-grid-2 {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
}

.form-grid-2[hidden] {
  display: none;
}

/* The neutral names for the borrowed rent-drawer-actions and rent-lines. A
   .form-actions is a wrapping row of buttons; a .line-list is a plain column
   of .list-row lines inside a padded card, drawer or dialog, with no card of
   its own (no overflow clip, no doubled gutter). Neither sets a size. */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.form-actions[hidden] {
  display: none;
}

.line-list {
  display: flex;
  flex-direction: column;
}

.line-list[hidden] {
  display: none;
}

.line-list > .list-row {
  padding: 6px 0;
}

/* The neutral names for the borrowed rent-late (a status or warning line in a
   form) and rent-note-signed (a shown signature). Colour and spacing only. */
.form-warning {
  color: var(--text-warning);
}

/* A message that reports a success: the status table's success tone, never the
   amber that means late or look again. */
.form-success {
  color: var(--text-success);
}

.signature-view {
  margin-top: 8px;
  color: var(--text-primary);
}

/* Text for a screen reader only: laid out off screen, not display:none, so it
   is still read. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Every placeholder in the app. None had a rule, so it was the browser's grey
   at 3.9:1. --text-muted is at least 4.7:1 on both input grounds in both
   themes; opacity 1 because some engines fade the placeholder on their own. */
::placeholder {
  color: var(--text-muted);
  opacity: 1;
}

/* Phone fallback for the list columns and the two-column form: below every
   rule it overrides. A multi-column row stacks to two columns: the first cell
   and the actions (the last cell) share the top line, and the cells between
   them each take a full line underneath. */
@media (max-width: 767px) {
  .list-row-assignments,
  .list-row-drivers,
  .list-row-standing,
  .list-row-vehicles,
  .list-row-places,
  .list-row-history {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .list-row-assignments > :last-child,
  .list-row-drivers > :last-child,
  .list-row-standing > :last-child,
  .list-row-vehicles > :last-child,
  .list-row-places > :last-child,
  .list-row-history > :last-child {
    grid-column: 2;
    grid-row: 1;
  }

  .list-row-assignments > :not(:first-child):not(:last-child),
  .list-row-drivers > :not(:first-child):not(:last-child),
  .list-row-standing > :not(:first-child):not(:last-child),
  .list-row-vehicles > :not(:first-child):not(:last-child),
  .list-row-places > :not(:first-child):not(:last-child),
  .list-row-history > :not(:first-child):not(:last-child) {
    grid-column: 1 / -1;
  }

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

/* ── Compliance: This week (the dashboard handoff) ──────────────────────────
   The page anatomy (PAGE ANATOMY above): a summary line where the four stat
   tiles were, then ONE card with a toolbar head (title, Sort, Cards / List),
   the list or the cards, and the rule the facility figure follows. */

/* The summary line: counts as <strong>, a band chip per band (the word and
   the count, never colour alone), the meetings queue as a bordered button.
   A band or a queue at zero is [hidden]; the hooks stay in the document. */
.dashboard-average {
  white-space: nowrap;
}

.dashboard-average strong,
.dashboard-band {
  font-variant-numeric: tabular-nums;
}

.dashboard-average strong.is-compliant { color: var(--text-success); }
.dashboard-average strong.is-marginal { color: var(--text-warning); }
.dashboard-average strong.is-non-compliant { color: var(--text-danger); }

.dashboard-pending {
  display: inline-flex;
}

.dashboard-pending[hidden] {
  display: none !important;
}

.dashboard-pending-btn {
  min-height: var(--control-filter-h);
  padding: 0 8px 0 4px;
  font-size: 12px;
  white-space: nowrap;
}

.dashboard-pending-btn .chip {
  padding: 1px 6px;
}

/* The card. No padding of its own (the toolbar, the rows and the footnote
   carry it) and no overflow clip, so the Sort menu can hang past its edge. */
.glass-card.dashboard-card {
  padding: 0;
}

.dashboard-card-title {
  font-size: 14px;
  font-weight: 650;
  white-space: nowrap;
  color: var(--text-primary);
}

.dashboard-card-sub {
  font-size: 12px;
  color: var(--text-secondary);
}

.dashboard-toolbar-end {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.dashboard-sort-trigger {
  min-height: var(--control-h);
  gap: 4px;
  padding: 0 8px 0 10px;
  font-size: 12px;
  white-space: nowrap;
}

.dashboard-sort .row-menu-item.is-current {
  font-weight: 600;
  background-color: var(--bg-card-hover);
}

/* A check mark as well as the weight and tint, so the current sort is not told
   by weight alone. */
.dashboard-sort .row-menu-item.is-current::after {
  content: "✓";
  margin-left: auto;
  padding-left: 12px;
}

/* The List: one grid for the column head and every row (--cols). Text tracks
   are minmax(0, …) so a long name ellipses instead of widening the card; the
   chip, progress and action tracks are fixed. */
.dashboard-list {
  --cols: minmax(0, 1.2fr) 200px 80px 150px minmax(0, 1fr) 170px;
}

.dashboard-row {
  min-height: 52px;
  /* The row opens the week's log, as the card does. */
  cursor: pointer;
}

.dashboard-cell-name {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.dashboard-meds {
  font-size: 11px;
  white-space: nowrap;
  color: var(--text-secondary);
}

.dashboard-cell-bed {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 6px;
}

.dashboard-bed {
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--text-secondary);
}

.dashboard-col-working {
  justify-self: center;
}

.dashboard-cell-working {
  display: flex;
  justify-content: center;
  color: var(--color-brand);
}

.dashboard-progress {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  white-space: nowrap;
  color: var(--text-secondary);
}

/* The progress rule on this card sits on the input well, as the handoff
   draws it; the fill is the band's hue (--card-status-color on the row). */
.dashboard-card .progress-track {
  background-color: var(--bg-input-focus);
}

/* Two classes: .btn-icon (6px padding) is later in the file. */
.btn-icon.dashboard-open-btn {
  padding: 0;
  border-radius: var(--border-radius-md);
  color: var(--text-secondary);
}

.dashboard-open-btn:hover {
  color: var(--text-primary);
  background-color: var(--bg-input-focus);
}

.dashboard-footnote {
  margin: 0;
  max-width: none;
  padding: 10px 14px;
  border-top: 1px solid var(--border-color-light);
}

/* The Cards: inside the same card, on the page's own background, each with a
   3px edge in its band's hue. */
.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 12px;
  padding: 12px 14px;
}

.client-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  padding: 12px 14px;
  background-color: var(--bg-app);
  border: var(--glass-border);
  border-left: 3px solid var(--card-status-color, var(--border-color));
  border-radius: var(--border-radius-lg);
  /* The whole card opens the week's log (operator decision Q-f), and this
     hover is its affordance. */
  cursor: pointer;
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
}

.client-card:hover {
  background-color: var(--bg-card-hover);
  border-color: var(--border-color);
  border-left-color: var(--card-status-color, var(--border-color));
}

/* Name | band chip, then the facts line across both. */
.client-card-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 2px 10px;
}

.client-card-header h4 {
  margin: 0;
  overflow: hidden;
  font-size: 15px;
  font-weight: 650;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.client-card-header .badge-compliance {
  white-space: nowrap;
}

.client-meta-line {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  font-size: 12px;
  color: var(--text-secondary);
}

.compliance-progress-section {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.progress-labels {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  font-size: 12px;
  color: var(--text-secondary);
}

.progress-labels span:last-child {
  flex-shrink: 0;
  white-space: nowrap;
}

/* A 4px rule, not an 8px capsule. It is a reading, and the FILL is the whole
   signal — which is why the fill keeps the base status hue rather than the text
   tint: nothing is written on it. */
.progress-track {
  width: 100%;
  height: 4px;
  background-color: var(--border-color-light);
  border-radius: 2px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  background-color: var(--card-status-color, var(--color-brand));
  border-radius: 2px;
  width: 0%;
  transition: width var(--transition-slow);
}

/* One line per requirement, a hairline above each; the count in the
   requirement's own tone (85 / 70, as before). */
.client-tasks-mini-list {
  display: flex;
  flex-direction: column;
  font-size: 13px;
}

.mini-task-item,
.mini-task-none {
  padding: 5px 0;
  border-top: 1px solid var(--border-color-light);
}

.mini-task-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.mini-task-none {
  font-size: 12px;
  text-align: center;
}

.mini-task-name {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.mini-task-stats {
  flex-shrink: 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--text-primary);
}

.mini-task-stats.warning-state {
  color: var(--text-warning);
}
.mini-task-stats.danger-state {
  color: var(--text-danger);
}
.mini-task-stats.success-state {
  color: var(--text-success);
}

/* The recovery bits | Log this week… and the chevron. */
.client-card-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: 6px;
  border-top: 1px solid var(--border-color-light);
  font-size: 12px;
  color: var(--text-secondary);
}

.client-card-actions {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: 4px;
}

/* Tablet landscape: taller rows and wider bed and action tracks. */
@media (max-width: 1199px) {
  .dashboard-list {
    --cols: minmax(0, 1.2fr) 190px 80px 150px minmax(0, 1fr) 190px;
  }

  .dashboard-row {
    min-height: 60px;
  }
}

/* Tablet portrait (the cards are forced): two across, and the toolbar's
   controls take a line of their own. */
@media (max-width: 999px) {
  .dashboard-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .dashboard-toolbar-end {
    flex-basis: 100%;
    margin-left: 0;
  }
}

/* Phone: one card across. */
@media (max-width: 767px) {
  /* The note under a card title is its own line here, so the "·" that joins it
     to the title on a wider screen has nothing to join (G2-121). */
  .dashboard-card-sub,
  .history-card-sub {
    flex-basis: 100%;
  }

  .card-sub-dot {
    display: none;
  }

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

/* Mouse sizes for the toolbar's Cards / List (the Sort button's height) and
   the 28px chevron. A (pointer: fine) block, so the 44px floors in the one
   (pointer: coarse) block still reach both on a touch screen. */
@media (pointer: fine) {
  .dashboard-toolbar .segmented-option {
    min-height: calc(var(--control-h) - 6px);
  }

  .dashboard-open-btn {
    min-width: var(--control-row-h);
    min-height: var(--control-row-h);
  }
}

/* Modal UI elements */
.modal-backdrop {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  background-color: var(--bg-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  transition: opacity var(--transition-normal);
}

.modal-container {
  background: var(--bg-card);
  border: var(--glass-border);
  border-radius: var(--border-radius-lg);
  box-shadow: var(--shadow-lg);
  max-width: 680px;
  width: 90%;
  max-height: 85vh;
  max-height: 85dvh;
  overflow-y: auto;
  --modal-pad: 32px;
  padding: var(--modal-pad);
  position: relative;
  transform: translateY(20px);
  transition: transform var(--transition-normal), opacity var(--transition-normal);
}

/* The footer stays in view while the card scrolls (walkthrough 2026-10-04: at
   a 495px window the buttons sat below the fold until the dialog was
   scrolled). The card is the scroller; the negative bottom and margin plus
   the matching padding, all from --modal-pad, pin the footer to the card's
   bottom edge, with the card colour behind it so scrolled content does not
   show through. A card that lays itself out with no padding sets --modal-pad
   to 0 below. Kept ABOVE the per-dialog footer rules (.client-detail-modal,
   .outing-history-dialog) so their own padding and background still win. */
.modal-container > .modal-footer {
  position: sticky;
  bottom: calc(-1 * var(--modal-pad));
  margin-bottom: calc(-1 * var(--modal-pad));
  padding-bottom: var(--modal-pad);
  background: var(--bg-card);
  z-index: 1;
}

.modal-container.client-detail-modal,
.modal-container.outing-history-dialog,
.modal-container.backfill-modal {
  --modal-pad: 0px;
}

.modal-backdrop:not(.hidden) {
  opacity: 1;
}

.modal-backdrop:not(.hidden) .modal-container {
  transform: translateY(0);
}

.hidden {
  display: none !important;
  opacity: 0 !important;
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 24px;
}

.modal-header h2 {
  font-size: var(--fs-h2);
  font-weight: 650;
}

/* ── W14: the dialog chrome is one contract ───────────────────────────────────
   Title and x pinned at the top of the scrolling card, the footer pinned at the
   bottom (above), one title size, one footer button size. A card that lays
   itself out with no padding sets --modal-pad to 0 and keeps its own header
   padding (the .client-detail-modal rule below, same specificity, later). The
   header's gap to the content is padding, not margin, so scrolled content does
   not show in it. Per-dialog title-size and button-size overrides are gone. */
.modal-container > :is(.modal-header, .feedback-header, .invite-header, .consent-header, .meeting-header) {
  position: sticky;
  top: calc(-1 * var(--modal-pad));
  z-index: 2;
  margin-top: calc(-1 * var(--modal-pad));
  padding-top: var(--modal-pad);
  background: var(--bg-card);
}

/* An h3 title (the dialogs that grew their own header class) is the same size. */
.modal-container :is(.modal-header, .feedback-header, .invite-header, .consent-header, .meeting-header) h3 {
  font-size: var(--fs-h2);
  font-weight: 650;
}

/* The footer buttons are one size, wherever the dialog came from. */
.modal-container .modal-footer > button,
.modal-container .modal-footer-actions > button {
  min-height: 44px;
}

/* The sheet's grab handle stays on top of the pinned header. */
@media (max-width: 767px) {
  .modal-container::before {
    z-index: 3;
    pointer-events: none;
  }
}

/* The portal invite / Invite staff lede sits a field's gap above the form. */
.invite-dialog > p.muted {
  margin: 0 0 16px;
  /* The dialog text size the other record dialogs use, not the 16px page size. */
  font-size: 14px;
  line-height: 1.5;
}

/* ── The client-detail modal ─────────────────────────────────────────────────
   SCOPED, and that is the whole point of the class. #modal-container is the
   shared shell for eight different dialogs; the rules below give this one a
   card with NO padding of its own, so each band inside it can carry its own
   padding and its own hairline and read as a document rather than as one
   32px box with four margins stacked in it. ClientDetailsModal adds the class
   on open and removes it on close. */
.client-detail-modal {
  max-width: 560px;
  padding: 0;
  border-radius: var(--border-radius-lg);
  border: 1px solid var(--border-color);
}

.client-detail-modal .modal-header {
  padding: 16px 20px 14px;
  margin-bottom: 0;
}

/* The score strip: chrome-coloured and hairlined top and bottom, so the one
   number somebody opened this modal to change is banded off from the header
   above it and the tabs below. */
.client-detail-modal #modal-live-score-panel {
  margin: 0;
  padding: 12px 20px;
  border-radius: 0;
  border-left: 0;
  border-right: 0;
  border-top: 1px solid var(--border-color-light);
  border-bottom: 1px solid var(--border-color-light);
  background: var(--bg-sidebar);
  box-shadow: none;
}

/* The second header line: what this window is, and the period it covers. */
.modal-subline {
  font-size: 12px;
  margin-top: 4px;
}

/* The score strip's contents: figure, band chip, bar and count on one row. */
.client-detail-modal #modal-live-score-panel {
  display: flex;
  align-items: center;
  gap: 14px;
}

.client-detail-modal #modal-live-score-panel { flex-wrap: wrap; }
.modal-score-unsaved { flex: 1 0 100%; font-size: 12px; }

.modal-score-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}

.modal-score-val {
  font-size: 22px;
  font-weight: 650;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.modal-score-val.is-compliant { color: var(--text-success); }
.modal-score-val.is-marginal { color: var(--text-warning); }
.modal-score-val.is-non-compliant { color: var(--text-danger); }

.modal-score-count {
  font-size: 11px;
  color: var(--text-secondary);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* The bar takes the leftover width between the figure and the count. */
.client-detail-modal .modal-score-track {
  flex: 1 1 auto;
  min-width: 40px;
}

/* The two carry-forward states. Both keep the .glass-card surface; the border
   says which one it is, and the 20px gutter comes from the modal scope. */
.carry-forward-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 14px;
  margin-bottom: 16px;
}

.carry-forward-banner.is-offer { border-color: var(--color-brand); }
.carry-forward-banner.is-loaded { border-color: var(--color-success); }
.carry-forward-banner.is-loaded .carry-forward-title { color: var(--text-success); }

.carry-forward-title {
  font-size: 13px;
  font-weight: 600;
}

.carry-forward-note {
  font-size: 12px;
  margin-top: 2px;
}

.client-detail-modal .modal-tabs {
  padding: 0 20px;
  margin-bottom: 0;
}

.client-detail-modal .modal-body {
  padding: 16px 20px;
  gap: 16px;
}

.client-detail-modal .modal-footer {
  margin-top: 0;
  padding: 12px 20px;
  background: var(--bg-sidebar);
  border-top: 1px solid var(--border-color-light);
}

/* The carry-forward offer sits between the strip and the tabs and needs the
   same 20px gutter the bands around it use. */
.client-detail-modal .carry-forward-banner {
  margin: 12px 20px 0;
}

/* In the log window the carry-forward offer is a band under the score strip,
   as wide as the dialog, not a card inside the card: no frame, no radius, a
   hairline below. The tone is a tint, not a border; the loaded state keeps its
   title colour (above). Below the two rules it overrides. */
.client-detail-modal .carry-forward-banner {
  margin: 0;
  padding: 10px 20px;
  border-radius: 0;
  border: 0;
  border-bottom: 1px solid var(--border-color-light);
  box-shadow: none;
}

.client-detail-modal .carry-forward-banner.is-offer {
  background: rgba(var(--color-brand-rgb), 0.08);
}

.client-detail-modal .carry-forward-banner.is-loaded {
  background: rgba(var(--compliant-rgb), 0.08);
}

/* On a phone the offer stacks: the text runs across the band and the button
   drops under it at the band's full width. */
@media (max-width: 767px) {
  .client-detail-modal .carry-forward-banner {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }

  .client-detail-modal .carry-forward-banner #carry-forward-btn {
    width: 100%;
    justify-content: center;
    text-align: center;
  }
}

/* G2-036: the score strip, the carry-forward offer and the tab strip are one
   band that stays under the pinned title while the body scrolls. The title's
   height is measured by ClientDetailsModal (--log-header-h); 64px is the
   fallback for a card that has not been measured. */
.client-detail-modal .log-pinned {
  position: sticky;
  top: var(--log-header-h, 64px);
  z-index: 1;
  background: var(--bg-card);
}

.modal-close-btn {
  color: var(--text-secondary);
  transition: color var(--transition-fast);
}

.modal-close-btn:hover {
  color: var(--text-primary);
}

/* ── Dynamically-created dialogs (feedback / invite) ──────────────────────────
   These reuse .modal-container for the card + reveal transform; the rules below
   style their internals with the shared tokens. The portal invite keeps the
   680px width the other record dialogs have (G2-097). */

/* The feedback card is a short form built out of full-width controls, so it
   wants a wider measure than the compact invite dialog. */
.feedback-dialog {
  max-width: 540px;
}

.feedback-header,
.invite-header,
.consent-header,
.meeting-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 20px;
}

.feedback-header h3,
.invite-header h3,
.consent-header h3,
.meeting-header h3 {
  font-size: var(--fs-h2);
  font-weight: 700;
}

/* The × close button in these headers (markup uses .modal-close, not the
   .modal-close-btn above). */
.modal-close {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 24px;
  line-height: 1;
  padding: 0 4px;
  color: var(--text-secondary);
  transition: color var(--transition-fast);
}

.modal-close:hover {
  color: var(--text-primary);
}

/* A dialog the shared shell (components/modal.js contentDialog) has marked busy.
   Its buttons are genuinely `disabled`, which is what stops a second submit and
   what takes them out of the Tab trap; this only says so visually. */
/* A dialog opened with size 'wide' (Vehicle records, Outings history) has room
   for a four-column list; the 90% width still holds it inside a small screen. */
.modal-container[data-modal-size="wide"] {
  max-width: 920px;
}

.modal-container[data-modal-busy="true"] {
  cursor: progress;
}

.modal-container[data-modal-busy="true"] .modal-close,
.modal-container[data-modal-busy="true"] .modal-footer button {
  opacity: 0.6;
}

/* The shell's heading takes focus when a dialog has no focusable control at all.
   It is focused programmatically (tabindex="-1"), never tabbed to, so the ring
   would be an outline on a heading nobody aimed at. */
.modal-container > [data-modal-title][tabindex="-1"]:focus,
.modal-container [data-modal-title][tabindex="-1"]:focus {
  outline: none;
}

.feedback-form,
.invite-form,
.consent-form,
.meeting-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.feedback-form label,
.invite-form label,
.consent-form label,
.meeting-form label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
}

.invite-form input,
.feedback-form textarea,
.consent-form input,
.consent-form textarea,
.consent-form select,
.meeting-form input,
.meeting-form select {
  width: 100%;
  background-color: var(--bg-input);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  padding: 10px 14px;
  font-size: 14px;
  color: var(--text-primary);
  font-family: inherit;
  transition: border-color var(--transition-fast);
}

/* Fields as tall as the other dialogs' .form-input rows (44px), not 40. */
.invite-form input:not([type="checkbox"]) {
  min-height: 44px;
}

.invite-form input:focus,
.feedback-form textarea:focus,
.consent-form input:focus,
.consent-form textarea:focus,
.consent-form select:focus,
.meeting-form input:focus,
.meeting-form select:focus {
  outline: none;
  border-color: var(--color-brand);
}

.feedback-form textarea {
  min-height: 132px;
  padding: 14px 16px;
  line-height: 1.5;
  resize: vertical;
}

/* ── The feedback card's own layout ───────────────────────────────────────────
   A stack of labelled sections: a small capitalised eyebrow, optional helper
   copy, then one full-width control. W14: the header is the shell's, the
   title and a trailing × like every dialog. */

.feedback-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Scoped to the form so it outranks the shared `.feedback-form label` rule
   above — the note's eyebrow IS a <label>, and a bare class would lose. */
.feedback-form .feedback-field-label {
  display: block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* Helper copy under an eyebrow: normal sentence case, tighter to the control
   it explains than the section gap would leave it. */
.feedback-hint {
  margin: -2px 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--text-secondary);
}

.feedback-toggle {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.feedback-toggle-btn {
  justify-content: center;
  width: 100%;
  padding: 14px 12px;
  text-align: center;
}

/* The selected toggle in each group. A neutral fill with the brand edge, as the
   .segmented control draws its chosen option: white on the brand teal was 2.8:1,
   and three brand-filled buttons made Send look like one of several. */
.feedback-toggle-btn.active,
.feedback-toggle-btn[aria-pressed="true"] {
  background-color: var(--bg-input-focus);
  border-color: var(--color-brand);
  color: var(--text-primary);
  font-weight: 600;
}

.feedback-errors,
.invite-errors,
.consent-errors,
.meeting-errors {
  background: rgba(var(--non-compliant-rgb), 0.1);
  border: 1px solid var(--color-danger);
  color: var(--color-danger);
  padding: 10px 12px;
  border-radius: var(--border-radius-md);
  font-size: 13px;
}

.consent-errors ul,
.meeting-errors ul {
  margin: 0;
  padding-left: 16px;
}

/* ── Consent modal internals (§2.31 capture/list/revoke) ─────────────────────
   The dialog reuses .modal-container (styled card) and the shared form/input/error
   rules above; these style the parts unique to consent — the local-storage notice,
   the stored-consent list, and the capture form's section headings. */
/* v1.46: one plain sentence under the title, as other dialogs have, not a
   shaded box. */
.consent-local-notice {
  font-size: 13px;
  color: var(--text-secondary);
  margin: 0 0 16px;
}

.consent-form h4 {
  font-size: var(--fs-h4);
  font-weight: 700;
  margin: 4px 0 0;
}

.consent-list {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin-bottom: 20px;
}

.consent-empty {
  font-size: 13px;
  color: var(--text-muted);
  margin: 0;
}

.consent-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 0;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border-color-light);
  border-radius: 0;
}

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

.consent-row-main {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.consent-recipient {
  font-weight: 600;
  font-size: 14px;
  color: var(--text-primary);
}

.consent-row-detail {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 12px;
  color: var(--text-secondary);
}

.consent-row-revocation {
  color: var(--text-muted);
}

.consent-row-actions {
  display: flex;
  justify-content: flex-end;
}

.consent-revoked-note {
  font-size: 12px;
  color: var(--color-danger);
}

.consent-expiry {
  display: flex;
  flex-direction: column;
  gap: 12px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  padding: 12px 14px;
  margin: 0;
}

.consent-expiry legend {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
  padding: 0 6px;
}

.consent-revocation-note {
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.5;
  margin: 0;
}

/* ── The §2.31(a)(3) category picker (ConsentModal.js) ───────────────────────
   Five checkboxes that say, in codes the system can check, what the prose above
   them covers. They are a REQUIRED multi-select, and they cannot be corrected
   after the consent is captured, so the block is bounded like .consent-expiry
   above it — the same bordered fieldset, because it is the same kind of thing:
   a group of controls answering one legal question.

   Two columns, because five long labels stacked single-file push the submit
   button off a laptop screen, and a capture form nobody can see the end of is
   the form people submit half-filled. The columns collapse at the app's 900px
   breakpoint (below), where the labels would otherwise wrap to three lines
   each. */
.consent-categories {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2px 16px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  padding: 12px 14px;
  margin: 0;
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

/* The legend is rendered in the border by every engine here, so the
   grid-column is defensive only — it costs nothing and keeps the heading full
   width in any engine that does treat it as a grid item. */
.consent-categories legend {
  grid-column: 1 / -1;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
  padding: 0 6px;
}

/* The "these boxes are the same scope, and they are final" paragraph. It
   explains the whole fieldset, so it spans both columns and sits above the
   first row rather than beside it. */
.consent-categories-note {
  grid-column: 1 / -1;
  margin: 0 0 8px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-muted);
}

/* SCOPED TO THE FORM ON PURPOSE. `.consent-form label` (0,1,1) is a 6px-gap
   COLUMN — it is what lays out every other field in this modal — and a bare
   `.consent-category` (0,1,0) loses to it, which would stack each checkbox
   above its own text. This selector is 0,2,0 and wins. */
.consent-form .consent-category {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  padding: 2px 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
  line-height: 1.35;
  cursor: pointer;
}

.consent-form .consent-category input {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  accent-color: var(--color-brand);
  cursor: pointer;
}

/* The box is the tab stop, and the label text is not focusable, so the ring has
   to be on the input itself or a keyboard user cannot tell where they are. */
.consent-form .consent-category input:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.consent-form .consent-category:hover {
  color: var(--color-brand);
}

/* INVALID: the submit came back with errors AND nothing is ticked.
   Not `:not(:has(:checked))` alone — that would paint the field red the moment
   the modal opened, i.e. before the user has been asked anything. The error box
   is the modal's own "this submission was refused" marker (it loses [hidden]
   when buildConsent returns field-named errors), so the two together say the
   one true thing: you submitted, and this is the field that was empty. The tint
   clears on the click that ticks a box, with no JavaScript involved.

   An engine without :has() simply gets the ordinary border — the message in the
   error box is the authority either way, never this tint. */
.consent-errors:not([hidden]) ~ .consent-categories:not(:has([data-consent-category]:checked)) {
  border-color: var(--color-danger);
  background-color: rgba(var(--non-compliant-rgb), 0.06);
}

.consent-errors:not([hidden]) ~ .consent-categories:not(:has([data-consent-category]:checked)) legend {
  color: var(--color-danger);
}

@media (max-width: 999px) {
  /* One category per line (the M3-9 pattern): at 390px a two-column row gives
     "Attendance and program participation" about 150px and it wraps to three
     lines, which is taller than the stack it was meant to save. */
  .consent-categories {
    grid-template-columns: minmax(0, 1fr);
  }

  /* WCAG 2.5.5. The whole label is the hit area — the 18px box alone is a
     quarter of a fingertip — so the row, not the input, carries the 44px. */
  .consent-form .consent-category {
    min-height: 44px;
  }

  .consent-form .consent-category input {
    width: 22px;
    height: 22px;
  }
}

/* ---------------------------------------------------------------------------
   The governing-consent picker (src/js/reports/print.js) — the dialog that asks
   what a client-scoped print is for before the report is rendered.

   It builds its own backdrop rather than using modal.js (it is a three-way
   choice with conditional fields, not an alert/confirm/prompt), so it reuses
   .modal-backdrop / .modal-container / .modal-header / .modal-body /
   .modal-footer and only the parts unique to it are styled here. The form is
   compact, so the card is narrower than the 680px default.
   --------------------------------------------------------------------------- */
.disclosure-picker {
  max-width: 520px;
}

.disclosure-picker-choices {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 16px;
}

/* A radio and its label on one line, with a 44px-tall hit area: this is pressed
   on a tablet at a med window, not only with a mouse. */
.disclosure-picker-option {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  font-size: 14px;
  color: var(--text-primary);
  cursor: pointer;
}

.disclosure-picker-option input {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  accent-color: var(--color-brand);
  cursor: pointer;
}

/* The conditional fields under a choice — indented so they read as belonging to
   the option above them. */
.disclosure-picker-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-left: 28px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
}

.disclosure-picker-field input,
.disclosure-picker-field select {
  width: 100%;
  min-height: 44px;
  background-color: var(--bg-input);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  padding: 10px 14px;
  font-size: 14px;
  font-weight: 400;
  color: var(--text-primary);
  font-family: inherit;
  transition: border-color var(--transition-fast);
}

.disclosure-picker-field input:focus,
.disclosure-picker-field select:focus {
  outline: none;
  border-color: var(--color-brand);
}

/* Consents on file that end on an event, named with why they are not offered
   (GG-024). Quiet text: it explains the list above, it is not a choice. */
.disclosure-picker-held {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-secondary);
}

.disclosure-picker-held p,
.disclosure-picker-held ul {
  margin: 0;
}

.disclosure-picker-held ul {
  padding-left: 20px;
}

.disclosure-picker-error {
  margin: 14px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--color-danger);
}

/* The line that names what the report carries, said BEFORE any choice is made.
   It is the premise of the coverage panel below, so it reads as a statement
   rather than as another field label. No margin of its own: it is a direct
   child of .modal-body, which is a 20px-gap flex column, and the paragraph
   above it keeps its UA margin — a top margin here would be the third helping
   of the same space. */
.disclosure-picker-categories {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-secondary);
}

/* ── The coverage panel ──────────────────────────────────────────────────────
   Filled by renderCoverage() for the selected consent, in one of three states:
   FULL (covers everything this report carries), PARTIAL (named categories will
   print as omitted blocks), LEGACY (no categories on record — a person has to
   read the signed scope text and tick).

   A BORDERED NOTE rather than another form field: it is the answer to what the
   operator just chose, not a thing to fill in. Indented to 28px so it lines up
   under the select it describes, the way .disclosure-picker-field is.

   THE STATE IS NEVER CARRIED BY COLOUR ALONE — every one of the three states
   says what it is in words, in the title paragraph. The green/amber accent is
   the app's existing status semantics restated, so the panel can be read at a
   glance by someone who already knows them, and loses nothing for anyone who
   cannot see them. */
.disclosure-picker-coverage {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 10px 0 0 28px;
  padding: 10px 12px;
  background-color: var(--bg-input);
  border: 1px solid var(--border-color);
  border-left: 3px solid var(--border-color);
  border-radius: var(--border-radius-md);
}

/* L-0009. The line above gives this box a `display`, which beats the UA
   `[hidden] { display: none }` rule at equal-ish weight, so the attribute
   renderCoverage() sets when no consent is selected would hide NOTHING without
   this restatement — an empty bordered box would sit under the select. */
.disclosure-picker-coverage[hidden] {
  display: none;
}

.disclosure-picker-coverage-title {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.5;
  color: var(--text-primary);
}

/* Green: nothing is being withheld and nothing is being asked of the operator. */
.disclosure-picker-coverage-title[data-coverage-state="full"] {
  color: var(--text-success);
}

/* Amber, for both of the states that mean "read this before you print": PARTIAL
   drops sections from the page, LEGACY cannot be decided by the machine at all.
   Neither is an error — a partial consent is a valid consent, and a legacy one
   is a lawful instrument — so neither is red. */
.disclosure-picker-coverage-title[data-coverage-state="partial"],
.disclosure-picker-coverage-title[data-coverage-state="legacy"] {
  color: var(--text-warning);
}

/* The panel's own edge follows its title. :has() only — a state class would
   have to be set by print.js, and this file does not get to ask for one.
   Without :has() the edge stays neutral and every word still reads. */
.disclosure-picker-coverage:has([data-coverage-state="full"]) {
  border-left-color: var(--color-success);
}

.disclosure-picker-coverage:has([data-coverage-state="partial"]),
.disclosure-picker-coverage:has([data-coverage-state="legacy"]) {
  border-left-color: var(--color-warning);
}

/* A labelled quotation: the consent's signed scope text, and beneath it the
   categories the report carries. Set off by a rule down the left because the
   scope half is VERBATIM from the instrument — the operator is being asked to
   compare two pieces of text, so they have to be visibly two pieces of text and
   not two more sentences of interface. */
.disclosure-picker-scope {
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-left: 2px solid var(--border-color);
  padding-left: 10px;
}

.disclosure-picker-scope-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}

.disclosure-picker-scope-text {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-primary);
  white-space: pre-wrap;
}

/* Only the consent's OWN words are quoted. The sibling block with the same
   class holds the report's category list, which this app wrote and which
   quotation marks would misattribute to the signed instrument. */
.disclosure-picker-scope[data-picker-consent-scope] .disclosure-picker-scope-text {
  font-style: italic;
}

.disclosure-picker-scope[data-picker-consent-scope] .disclosure-picker-scope-text::before {
  content: "\201C";
}

.disclosure-picker-scope[data-picker-consent-scope] .disclosure-picker-scope-text::after {
  content: "\201D";
}

/* The tick that is the only thing standing between a legacy consent and a
   print. It is a decision, not a preference, so it gets a bounded amber row of
   its own rather than sitting in the panel's text flow — and a 44px one,
   because this dialog is used at a med window on a tablet. */
.disclosure-picker-confirm {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 8px 12px;
  background-color: rgba(var(--marginal-rgb), 0.1);
  border: 1px solid var(--color-warning);
  border-radius: var(--border-radius-md);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--text-primary);
  cursor: pointer;
}

.disclosure-picker-confirm input {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  accent-color: var(--color-brand);
  cursor: pointer;
}

.disclosure-picker-confirm input:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

@media (max-width: 999px) {
  /* The 28px indent is a desktop alignment cue; on a 390px screen it is 7% of
     the panel's width spent on nothing. */
  .disclosure-picker-coverage {
    margin-left: 0;
  }

  .disclosure-picker-confirm input {
    width: 22px;
    height: 22px;
  }
}

/* Invite "created" state — the one-time link row. */
.admin-link-warning {
  font-size: 13px;
  color: var(--text-secondary);
  margin: 0 0 12px;
}

.admin-link-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.admin-link-value {
  flex: 1 1 240px;
  min-width: 0;
  background-color: var(--bg-input);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-sm);
  padding: 8px 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--text-primary);
  word-break: break-all;
}

.admin-copy-btn {
  white-space: nowrap;
}

/* ── Admin dashboard — staff and resident rows (Phase 11 R4g) ───────────────
   Each list is one F1 list card (`glass-card list-card admin-eval-list` /
   `admin-resident-list`) of .list-row rows: the identity (email or name and
   its status chip) with the dotted meta under it, the actions at the top
   right, and the one-time link, when revealed, a full-width line of its own
   at the foot of its row. Below .list-row and .list-row-actions, which these
   rules override. */
.admin-eval-list {
  /* Match .grid-stats' bottom margin so the "Feedback" heading below sits the
     same distance from this list as "Evaluators & sign-ins" does from the tiles
     above it — consistent section spacing down the page. */
  margin-bottom: 32px;
}

/* v1.44: each staff row's ⋯ menu (rowMenu.js, position absolute) must draw
   past the last row, so this list card does not clip (the Requirements
   library's rule). */
.admin-eval-list {
  overflow: visible;
}

.admin-eval-card,
.admin-resident-card {
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 4px 10px;
  padding: 10px 16px;
}

.admin-eval-card > .admin-eval-main,
.admin-resident-card > .admin-eval-main {
  grid-column: 1;
  grid-row: 1;
  justify-content: flex-start;
  gap: 4px 10px;
  min-width: 0;
}

.admin-eval-card > .admin-eval-meta,
.admin-resident-card > .admin-eval-meta {
  grid-column: 1;
  grid-row: 2;
  min-width: 0;
}

.admin-eval-card > .admin-row-actions,
.admin-resident-card > .admin-row-actions {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
}

/* The one-time link, when revealed, spans the row under the buttons. */
.admin-eval-card > .admin-link-reveal,
.admin-resident-card > .admin-link-reveal {
  grid-column: 1 / -1;
  grid-row: 3;
  min-width: 0;
  padding-top: 6px;
}

.admin-eval-main {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* The account's name over its email (operator Q101: B). */
.admin-eval-ident {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.admin-eval-name {
  font-weight: 600;
  font-size: 15px;
  color: var(--text-primary);
  word-break: break-word;
}

.admin-eval-unnamed {
  font-weight: 500;
  font-style: italic;
  color: var(--text-muted);
}

.admin-eval-email {
  font-size: 13px;
  color: var(--text-secondary);
  word-break: break-word;
}

.admin-eval-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-secondary);
}

.admin-eval-sep { color: var(--text-muted); }
.admin-eval-you { color: var(--color-brand); }

.admin-eval-source {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--text-muted);
}

.admin-eval-actions {
  align-items: center;
}

/* Phone: the actions take a line of their own under the meta, at its start,
   and the link under them. */
@media (max-width: 767px) {
  .admin-eval-card,
  .admin-resident-card {
    grid-template-columns: minmax(0, 1fr);
  }

  .admin-eval-card > .admin-row-actions,
  .admin-resident-card > .admin-row-actions {
    grid-column: 1;
    grid-row: 3;
    justify-content: flex-start;
  }

  .admin-eval-card > .admin-link-reveal,
  .admin-resident-card > .admin-link-reveal {
    grid-row: 4;
  }
}

/* ── Admin dashboard — feedback rows ─────────────────────────────────────────
   One list card (`glass-card list-card admin-feedback-list`) of one-column
   rows: a head line of chips (sentiment + kind) and dotted meta (author ·
   screen · time), then the note. */
.admin-feedback-item {
  grid-template-columns: minmax(0, 1fr);
  padding: 10px 16px;
}

.admin-feedback-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-secondary);
}

.feedback-author {
  font-weight: 600;
  color: var(--text-primary);
  word-break: break-word;
}

.feedback-screen {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--text-muted);
}

/* Push the timestamp to the right edge of the head row. */
.feedback-at {
  margin-left: auto;
  white-space: nowrap;
}

.admin-feedback-note {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-primary);
  white-space: pre-wrap;
  word-break: break-word;
}

/* ── The resident notice card ───────────────────────────────────────────────
   This card shipped with class names and no rules at all: the live pass of
   2026-09-18 enumerated the loaded stylesheets and found ZERO selectors
   matching `admin-notice-*`, against seven for `admin-release-*` immediately
   below. The result was not "plain": the textarea took the UA default of about
   twenty columns — 139px wide — with a WHITE background inside a dark card, no
   padding, and the "New version" label overlapped by the box.

   That box is where an operator pastes the resident notice, which is a
   compliance document of a few thousand characters that a facility answers for.
   Sizing it to a phone number is how the wrong text goes live.
   ─────────────────────────────────────────────────────────────────────────── */
.admin-notice-state,
.admin-notice-pending {
  margin: 0 0 12px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-primary);
}

/* A saved-but-not-live version is the one thing on this card that should catch
   the eye: it is the state in which pressing Activate changes what residents
   read. */
.admin-notice-pending {
  color: var(--color-warning);
}

.admin-notice-label {
  display: block;
  margin: 16px 0 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
}

.admin-notice-textarea {
  display: block;
  width: 100%;
  min-height: 260px;
  padding: 12px;
  box-sizing: border-box;
  background-color: var(--bg-input);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  font-family: inherit;
  font-size: 13px;
  /* The portal renders the body with `white-space: pre-wrap`, so the blank
     lines the operator types here are the paragraph breaks a resident sees.
     Matching the line-height means the shape in this box is the shape on the
     phone rather than a rough guide to it. */
  line-height: 1.55;
  resize: vertical;
}

.admin-notice-textarea:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.admin-notice-error {
  margin: 8px 0 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-danger);
}

/* The error paragraph carries the `hidden` ATTRIBUTE. The rule above sets no
   `display`, so the UA's `[hidden] { display: none }` still wins TODAY and this
   line changes nothing right now. It is here as a guard, not a fix: the moment
   anyone adds a `display` to `.admin-notice-error` — to make it a flex row with
   an icon, say — the UA rule loses on specificity and the refusal text shows
   permanently, including before there is a refusal. That failure is silent and
   has already been paid for three times in this sheet. */
.admin-notice-error[hidden] {
  display: none;
}

.admin-notice-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 14px;
}

.admin-notice-draft,
.admin-notice-current {
  margin-top: 16px;
  font-size: 12px;
  color: var(--text-secondary);
}

.admin-notice-draft > summary,
.admin-notice-current > summary {
  cursor: pointer;
  padding: 6px 0;
  font-weight: 600;
  color: var(--text-secondary);
}

/* The frozen text of a version, shown for reading back before activation.
   `pre-wrap` and `break-word` for the same reason the portal's own body block
   has them: this is operator-pasted prose, and one long unbroken string must
   not push the card sideways. Capped and scrollable so a long notice does not
   bury the rest of the dashboard. */
.admin-notice-body-text {
  margin: 0;
  max-height: 320px;
  overflow-y: auto;
  padding: 12px;
  background-color: var(--bg-input);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ── Release notes ──────────────────────────────────────────────────────────
   Deliberately the same list-card shape as the feedback section above: both
   are a reverse-chronological list of short entries, one-column rows in one
   list card (Phase 11 R4g). */
.admin-release-item {
  grid-template-columns: minmax(0, 1fr);
  padding: 10px 16px;
}

.admin-release-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
}

.admin-release-version {
  font-size: 15px;
  font-weight: 650;
  color: var(--text-primary);
}

.admin-release-date {
  font-size: 13px;
}

.admin-release-changes {
  /* The list keeps its markers, indented far enough to clear the card padding
     rather than hanging outside it. */
  margin: 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-secondary);
}

/* The running version, beside the section heading. Small print: an admin who
   wants it is checking which build they are on, and everyone else is reading
   the notes underneath. */
.admin-release-current {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
}

.modal-body {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* Modal Form Controls */
.modal-row-item {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border-color-light);
}

.modal-row-item:last-child {
  border-bottom: none;
}

.requirement-detail-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-weight: 600;
}

.requirement-detail-title label {
  font-size: 15px;
}

/* R2d: the requirement-badge labels (bed, location, target, occupant) are
   neutral chips. A bed label joins free-text house, room and bed names, so it
   can be longer than a phone row: it wraps inside its row instead of the
   chip's nowrap pushing the page sideways. */
.chip.requirement-badge {
  white-space: normal;
  overflow-wrap: anywhere;
  min-width: 0;
  max-width: 100%;
}

/* R2f: the rent row's "N reported payments to review" chip is long text; on a
   phone it wraps inside the row instead of the chip's nowrap pushing the page
   sideways. Below .chip. */
.rent-row .roster-pending-badge {
  white-space: normal;
  min-width: 0;
  max-width: 100%;
}

/* Daily Checklist (M-Su) UI */
.daily-checkbox-group {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
  margin-top: 8px;
}

.day-checkbox-label {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 0;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--text-muted);
  cursor: pointer;
  position: relative;
}

/* The real checkbox stays in the page, unseen, so Tab reaches it and Space
   ticks it; the label's box beside it (.custom-checkbox-ui) is what shows and
   takes the ring. It was display: none, which took every dose and day box out
   of the Tab order, and made the day window's first focus skip to its Note
   field, raising a tablet's keyboard (tablet walk, 2026-10-10). The weekly
   log's yes/no box (.boolean-checkbox-input) is drawn the same way; it had
   no rule at all, so its native box showed beside a box that never filled. */
.day-checkbox-input,
.boolean-checkbox-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.custom-checkbox-ui {
  width: 100%;
  height: 36px;
  border-radius: var(--border-radius-sm);
  background-color: var(--bg-input-focus);
  border: 1px solid var(--border-color);
  display: flex;
  align-items: center;
  justify-content: center;
  color: transparent;
  transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

@media (min-width: 1000px) {
  .custom-checkbox-ui {
    height: 30px;
  }
}

.day-checkbox-label:hover .custom-checkbox-ui {
  border-color: var(--color-brand);
}

.day-checkbox-input:checked + .custom-checkbox-ui,
.boolean-checkbox-input:checked + .custom-checkbox-ui {
  background-color: var(--color-brand);
  border-color: var(--color-brand);
  color: var(--color-brand-fg);
}

.day-checkbox-input:checked + .custom-checkbox-ui svg,
.boolean-checkbox-input:checked + .custom-checkbox-ui svg {
  opacity: 1;
}

.custom-checkbox-ui svg {
  opacity: 0;
  transition: opacity var(--transition-fast);
}

/* Count-Based target input */
.count-input-group {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}

/* The minus, the value and the plus share one border and one radius, with
   hairlines between them. The mobile block further down still grows the two
   buttons to 44px; only the desktop size came down. */
.count-input-group .counter-btn:first-child {
  border-radius: var(--border-radius-md) 0 0 var(--border-radius-md);
}

.count-input-group .count-plus {
  border-radius: 0 var(--border-radius-md) var(--border-radius-md) 0;
  border-left: 1px solid var(--border-color);
}

.counter-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  background-color: var(--bg-card);
  border: 1px solid var(--border-color);
  font-size: 16px;
  font-weight: 600;
  transition: background-color var(--transition-fast);
}

.counter-btn:hover {
  background-color: var(--bg-card-hover);
}

.counter-value {
  width: 40px;
  height: 30px;
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-left: 0;
  border-right: 0;
}

.count-fraction-label {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-secondary);
}

/* Shown beside the counter once the week's target is met. */
.count-met-label {
  margin-left: auto;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-success);
}

/* Text Area clinical notes */
.notes-input-wrapper {
  margin-top: 8px;
}

.text-area-notes {
  width: 100%;
  min-height: 44px;
  background-color: var(--bg-sidebar);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  padding: 8px 10px;
  font-size: 12px;
  resize: vertical;
  transition: border-color var(--transition-fast);
}

.text-area-notes:focus {
  outline: none;
  border-color: var(--color-brand);
}

.modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 24px;
  padding-top: 16px;
  border-top: 1px solid var(--border-color-light);
}

/* A quiet text button on the left of the footer, for the action that changes
   nothing. It used to be a .btn-secondary sitting beside Save at the same
   weight, which is three equal-looking buttons for one decision. */
.modal-footer-link {
  margin-right: auto;
  font-size: 12px;
  color: var(--text-secondary);
  transition: color var(--transition-fast);
}

.modal-footer-link:hover {
  color: var(--text-primary);
  text-decoration: underline;
}

.modal-footer-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* In-app dialog (components/modal.js) touch sizing — lifted out of the inline
   styles the component used to set on every open. The confirm/cancel/dismiss
   buttons all carry [data-modal-confirm] or [data-modal-cancel], so this pair
   is the whole button set; the ≥44×44 minimum is kept at ALL widths (the
   destructive confirm/cancel must be comfortable on desktop too, exactly as the
   old inline min-width/min-height did). */
[data-modal-confirm],
[data-modal-cancel] {
  min-width: 44px;
  min-height: 44px;
}

/* The promptDialog text field, styled off the hook the component sets rather
   than inline. Full width, a comfortable box, and the same 44px min touch
   height the inline rule guaranteed. */
[data-modal-input] {
  width: 100%;
  padding: 10px 12px;
  min-height: 44px;
  /* Explicit tokens, not inheritance: without these the input keeps the UA's
     white background while inheriting the dark theme's white text — an
     unreadable white-on-white box (reported live on the Confirm Count modal). */
  background-color: var(--bg-input);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
}

/* Modal tabs: the strip is .tabs-underline (display, gap, hairline, the tab
   size and the chosen underline); this keeps its spacing, and at desktop
   widths lets the strip draw in full rather than scroll (the log window's four
   labels run about 20px into the strip's side padding at 1280). The <=900px
   rule below turns the scroller back on. */
.modal-tabs {
  margin-bottom: 24px;
  overflow-x: visible;
}

.modal-tab-content {
  display: none;
  animation: slideIn 0.2s ease;
}

.modal-tab-content.active {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* ── The resident record page (Phase 11 X4a; the overhaul's record handoff) ──
   One resident: a summary line (name and chips, a facts line, Log this week…
   and Next resident at the right; on a phone those two are the pinned bottom
   bar), the underline tab strip, and the chosen tab's cards. Cards use the
   style guide's h5 heading (11px uppercase). Every class here that sets
   display restates [hidden]. */
.resident-record {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.resident-record[hidden] {
  display: none;
}

/* The summary line. Below .view-summary (PAGE ANATOMY), whose margin and
   centring it overrides: the name block can run to two lines. It wraps: a
   long name pushes Log this week and Next resident to their own line rather
   than squeezing the chips, so a status word is never clipped. */
.resident-record-head {
  align-items: flex-start;
  gap: 10px 16px;
  margin: 0;
}

.resident-record-head[hidden] {
  display: none;
}

.resident-record-identity {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1 1 320px;
  min-width: 0;
}

.resident-record-identity[hidden] {
  display: none;
}

.resident-record-title-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  min-width: 0;
}

.resident-record-title-line[hidden] {
  display: none;
}

.resident-record-name {
  margin: 0;
  font-size: 18px;
  font-weight: 650;
  line-height: 1.3;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.resident-record-facts {
  margin: 0;
  font-size: 12px;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.resident-record-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.resident-record-chips[hidden] {
  display: none;
}

.resident-record-panel {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.resident-record-panel[hidden] {
  display: none;
}

/* The style guide's h5: a card's section heading. */
.resident-record-section-title {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.resident-record-section-note {
  font-size: 12px;
  margin: 0;
  overflow-wrap: anywhere;
}

/* The record page has six tabs on one line. On a phone the strip scrolls
   sideways with its scrollbar hidden, and the last tab peeks at the edge as
   the sign there is more (the record handoff; the phone rule is in the
   record's phone block below). overflow-y is clipped so the strip never reads
   as a vertical scroller. Below the underline-tabs rules it overrides. */
.resident-record-tabs {
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}

.resident-record-tabs::-webkit-scrollbar {
  display: none;
}

/* On a phone the six tabs do not fit. The fade is the cue that the strip
   swipes; the padding after the last tab is as wide as the fade, so at the end
   of the strip Profile is clear of it. */
@media (max-width: 767px) {
  .resident-record-tabs {
    padding-right: 24px;
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(90deg, #000 calc(100% - 24px), transparent);
  }
}

.resident-record-tabs[hidden] {
  display: none;
}

/* The pending-meetings count on the Meetings tab. */
.resident-record-tab-count {
  margin-left: 6px;
  padding: 0 6px;
}

/* A card's head: the heading (and its note) at the left, a count or one
   button at the right. */
.resident-record-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
  align-self: stretch;
  min-width: 0;
}

.resident-record-card-head[hidden] {
  display: none;
}

.resident-record-card-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  flex: 1 1 240px;
  min-width: 0;
}

.resident-record-card-heading[hidden] {
  display: none;
}

.resident-record-card-count {
  font-size: 12px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

/* Every card on the page: the style guide's 14px 16px. Two classes, so it
   beats .glass-card's 16px wherever it sits. */
.resident-record .glass-card {
  padding: 14px 16px;
}

/* The cards on Meds, Money, Outings and Profile: a column of a heading, any
   facts, a note and buttons. The buttons keep their own width
   (align-items: flex-start) and wrap in a row. The three link cards hold to
   560px; Meetings is a list and runs the width. */
.resident-record-link-card,
.resident-record-actions-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  min-width: 0;
}

.resident-record-link-card[hidden],
.resident-record-actions-card[hidden] {
  display: none;
}

/* Full width, like the Meetings card on the tab beside it. At 560px the
   Medications, Rent and Out / Back cards filled the left 55% of the page and
   changed width from tab to tab. */
.resident-record-link-card {
  max-width: none;
}

.resident-record-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  max-width: 100%;
}

.resident-record-buttons[hidden] {
  display: none;
}

/* A fact list: label | value, one row each. */
.resident-record-dl {
  display: grid;
  gap: 6px;
  align-self: stretch;
  margin: 0;
  font-size: 13px;
}

.resident-record-dl[hidden] {
  display: none;
}

.resident-record-dl-row {
  display: grid;
  grid-template-columns: 100px minmax(0, 1fr);
  gap: 12px;
}

.resident-record-dl-row[hidden] {
  display: none;
}

.resident-record-dl dt {
  font-size: 12px;
  color: var(--text-secondary);
}

.resident-record-dl dd {
  margin: 0;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

/* Week: the twelve weeks above This week, one column. */
.resident-record-week {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: 14px;
}

.resident-record-week[hidden] {
  display: none;
}

.resident-record-this-week {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}

.resident-record-this-week[hidden] {
  display: none;
}

.resident-record-this-week .card-empty {
  padding: 14px 0 6px;
}

/* The band bar: the week's score as a 4px bar in the band's colour. */
.resident-record-band-bar {
  height: 4px;
  overflow: hidden;
  border-radius: var(--border-radius-full);
  background-color: var(--bg-input-focus);
}

.resident-record-band-bar > span {
  display: block;
  width: calc(var(--pct, 0) * 1%);
  height: 100%;
  border-radius: var(--border-radius-full);
  background-color: var(--text-secondary);
}

.resident-record-band-bar.band-success > span {
  background-color: var(--color-success);
}

.resident-record-band-bar.band-warning > span {
  background-color: var(--color-warning);
}

.resident-record-band-bar.band-danger > span {
  background-color: var(--color-danger);
}

.resident-record-reqs {
  display: flex;
  flex-direction: column;
}

.resident-record-reqs[hidden] {
  display: none;
}

/* One requirement: name | count | Met (when reached). */
.resident-record-req {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 64px 40px;
  align-items: center;
  gap: 10px;
  min-height: 34px;
  font-size: 13px;
  border-top: 1px solid var(--border-color-light);
}

.resident-record-req[hidden] {
  display: none;
}

.resident-record-req-name {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--text-primary);
}

.resident-record-req-count {
  text-align: right;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.resident-record-req.is-met .resident-record-req-count {
  color: var(--text-success);
}

.resident-record-req-chip {
  display: flex;
  justify-content: flex-end;
}

.resident-record-req-chip[hidden] {
  display: none;
}

.resident-record-this-week .view-footnote {
  margin: 0;
}

.resident-record-weeks .resident-record-card-head {
  margin-bottom: 10px;
}

/* The twelve tiles, six across (four on a phone), on the card itself rather
   than in a second filled box. Two classes, so it beats the shared
   .heatmap-week-grid (the Trend tab's .is-trend is not drawn here). */
.resident-record-weeks .heatmap-week-grid {
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 8px;
  padding: 0;
  border: 0;
  background-color: transparent;
}

/* Tablet portrait: six across left a 46px tile, too narrow for 'compliant' at
   11px (it ran past the border). Four across, as on a phone. Below the base
   rule on purpose: a media query adds no specificity. */
@media (max-width: 999px) {
  .resident-record-weeks .heatmap-week-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* Meetings: the list, inline; Review… opens the meeting window. */
.resident-record-meetings-card {
  max-width: none;
  align-items: stretch;
  gap: 0;
}

.resident-record .resident-record-meetings-card {
  padding: 0;
}

.resident-record-meetings-card > .resident-record-card-head {
  padding: 12px 16px;
}

.resident-record-meeting-list {
  border-top: 1px solid var(--border-color-light);
}

.resident-record-meeting-list[hidden] {
  display: none;
}

.resident-record-meeting {
  --cols: minmax(0, 1fr) auto;
  padding: 10px 16px;
}

/* A meeting waiting for a decision: the decision strip's tint, lighter. */
.resident-record-meeting.is-pending {
  background-color: rgba(var(--marginal-rgb), 0.05);
}

.resident-record-meeting-main {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.resident-record-meeting-main[hidden] {
  display: none;
}

.resident-record-meeting-name {
  font-size: 13px;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.resident-record-meeting-name strong {
  font-weight: 600;
  color: var(--text-primary);
}

.resident-record-meeting-evidence {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.resident-record-meeting-evidence[hidden] {
  display: none;
}

.resident-record-meeting-foot {
  margin: 0;
  max-width: none;
  padding: 10px 16px 12px;
  border-top: 1px solid var(--border-color-light);
}

.resident-record-meetings-card .card-empty {
  border-top: 1px solid var(--border-color-light);
}

/* History: the notes brought in from earlier records, read only. The card is the
   meetings card's shape (head padded, the list flush to the edges). The note text
   keeps its line breaks and wraps anywhere, so a long word cannot widen a phone. */
.resident-record .resident-history-card {
  padding: 0;
}

.resident-history-card > .resident-record-card-head {
  padding: 12px 16px;
}

.resident-history-card .card-empty {
  border-top: 1px solid var(--border-color-light);
}

.resident-history-list {
  border-top: 1px solid var(--border-color-light);
}

.resident-history-note {
  padding: 10px 16px;
}

.resident-history-note + .resident-history-note {
  border-top: 1px solid var(--border-color-light);
}

.resident-history-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 12px;
  font-size: 13px;
  color: var(--text-secondary);
}

.resident-history-date {
  font-weight: 600;
  color: var(--text-primary);
}

.resident-history-text {
  margin: 4px 0 0;
  max-width: 75ch;
  font-size: 14px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.resident-history-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  padding: 10px 16px 12px;
  border-top: 1px solid var(--border-color-light);
}

/* Profile: one stack of full-width cards, like the other tabs: the facts, then
   Rules and history (and its panel), Records, Leaving (operator 2026-10-15). */
.resident-record-profile {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.resident-record-profile[hidden] {
  display: none;
}

.resident-record-profile-col {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.resident-record-profile-col[hidden] {
  display: none;
}

/* Leaving: the heading in the danger colour, Discharge or Re-admit, then
   Delete under a hairline. */
.resident-record-leaving > .resident-record-section-title {
  color: var(--text-danger);
}

.resident-record-danger {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  align-self: stretch;
  padding-top: 10px;
  border-top: 1px solid var(--border-color-light);
}

.resident-record-danger[hidden] {
  display: none;
}

/* Delete resident…, outlined in the danger colour, never filled. Three
   classes, below .btn-secondary and its :hover. */
.resident-record .btn-secondary.is-danger {
  color: var(--text-danger);
  border-color: var(--color-danger);
}

.resident-record .btn-secondary.is-danger:hover {
  background-color: rgba(var(--non-compliant-rgb), 0.12);
  border-color: var(--color-danger);
}

/* The Customize rules card on Profile (Phase 11 X4e): the roster row's drawer
   (.client-config-panel), drawn in its own card under the Profile facts. Sets
   no display, so the [hidden] rule above holds. */
.resident-record-config-card {
  min-width: 0;
}

/* Grant credit… and Deny credit…: the same bordered button, the same weight
   (600), side by side. */
.resident-record-decide {
  font-weight: 600;
  white-space: nowrap;
}

/* A fact that needs doing, in its tone (the shelf on Meds). */
.resident-record-fact-danger {
  font-weight: 600;
  color: var(--text-danger);
}

.resident-record-fact-warning {
  font-weight: 600;
  color: var(--text-warning);
}

/* Next resident ›: the chevron after the words. */
.resident-record-next {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.resident-record-next[hidden] {
  display: none;
}

/* Phone. */
@media (max-width: 767px) {
  /* The tabs keep their width and the strip scrolls (see .resident-record-tabs). */
  .resident-record-tabs .tab {
    flex: 0 0 auto;
  }

  .resident-record-weeks .heatmap-week-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  /* Buttons share the line, two to a row. The row takes the card's width: the
     card's align-items: flex-start would shrink it to its buttons, and the
     40% basis of a shrunken row wrapped 'Open Out / Back' onto two lines. */
  .resident-record-buttons {
    align-self: stretch;
  }

  .resident-record-buttons > * {
    flex: 1 1 40%;
    justify-content: center;
    white-space: nowrap;
  }

  .resident-record-link-card > [data-record-open] {
    align-self: stretch;
    justify-content: center;
  }

  /* A meeting stacks: name, evidence, then its word and Review…. */
  /* One column: the shared phone .table-row rule (above) sets two, which
     would squeeze the buttons beside the name. */
  .resident-record-meeting {
    --cols: minmax(0, 1fr);
    grid-template-columns: minmax(0, 1fr);
    padding: 12px 14px;
  }

  .resident-record-meeting .table-row-actions {
    justify-content: flex-start;
  }

  /* Grant credit… and Deny credit… share the line, equal. */
  .resident-record-meeting .resident-record-decide {
    flex: 1 1 0;
    justify-content: center;
  }

  .resident-record-dl-row {
    grid-template-columns: 96px minmax(0, 1fr);
  }
}

/* The roster row's chevron into the record page. 32px with a mouse; the
   (pointer: coarse) block lifts it to 44. */
.roster-open-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: auto;
  min-width: 32px;
  min-height: 32px;
  border-radius: var(--border-radius-md);
  color: var(--text-secondary);
  transition: color var(--transition-fast), background-color var(--transition-fast);
}

.roster-open-btn[hidden] {
  display: none;
}

.roster-open-btn:hover {
  color: var(--text-primary);
  background-color: var(--bg-input-focus);
}

.report-section-title {
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  color: var(--text-secondary);
  letter-spacing: 0.4px;
  margin-bottom: 10px;
  border-bottom: 1px solid var(--border-color-light);
  padding-bottom: 6px;
}

/* The work-hours panel's own header row, matching that title treatment. */
.work-schedule-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.work-schedule-head .report-section-title {
  margin-bottom: 0;
  border-bottom: 0;
  padding-bottom: 0;
}

.work-hours-total {
  font-size: 13px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.work-hours-total-label {
  font-size: var(--fs-xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-muted);
  margin-right: 6px;
}

.report-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.schedule-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.compliance-banner {
  background: rgba(var(--non-compliant-rgb), 0.1);
  border: 1px solid var(--color-danger);
  color: var(--text-danger);
  padding: 12px;
  border-radius: var(--border-radius-md);
  font-size: 12px;
  font-weight: 600;
  display: flex;
  gap: 8px;
  align-items: flex-start;
  margin-bottom: 16px;
}

/* The same banner in the warning band — a medication note, not a refusal. */
.compliance-banner.is-warning {
  background: rgba(var(--marginal-rgb), 0.1);
  border-color: var(--color-warning);
  color: var(--text-warning);
}

/* Client Roster List / Form Layouts */

/* The collapsible form-card header. No view draws one since the roster's admit
   band became a dialog (the overhaul's roster handoff); kept because the
   coarse-pointer block and the focus-ring list still name it. */
.form-card-toggle {
  appearance: none;
  background: none;
  border: none;
  padding: 0;
  margin: 0 0 16px 0;
  width: 100%;
  cursor: pointer;
  color: inherit;
  font: inherit;
  text-align: left;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  user-select: none;
}
.form-card-toggle:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--border-radius-sm);
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 16px;
  /* An input's intrinsic min-width (its default `size`) otherwise forces a grid
     track wider than its share and overruns the panel border. */
  min-width: 0;
}

.form-group label {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
}

.form-input {
  background-color: var(--bg-input);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  padding: 10px 14px;
  font-size: 14px;
  transition: border-color var(--transition-fast);
  min-width: 0;
  max-width: 100%;
}

.form-input:focus {
  outline: none;
  border-color: var(--color-brand);
}

/* The Medications tab strip sits on .tabs-underline. This keeps the spacing,
   and wraps the strip rather than scrolling it (as .resident-record-tabs
   does) so long labels stay whole at 390px. */
.tabs-header {
  margin-bottom: 16px;
  flex-wrap: wrap;
  overflow-x: visible;
}

/* Dead while the button also carries .tab: .tabs-underline .tab (0,2,0) sets
   the size, colour and chosen underline. Kept because the coarse-pointer
   source-order guard needs a base rule for this class above that block. */
.tab-btn {
  padding: 10px 2px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
  transition: color var(--transition-fast), box-shadow var(--transition-fast);
}

/* ── No scroll area inside the page scroll (Phase 11 X1c) ────────────────────
   The list takes its content height and the page scrolls once. Do not give it
   a max-height or an overflow of its own (test/meta/roster-layout.test.js). */
.roster-list {
  display: flex;
  flex-direction: column;
}

/* ══════════════════════════════════════════════════════
   THE RESIDENT ROSTER (the overhaul's roster handoff)
   A summary line (active, open beds, the meetings waiting, Admit resident…
   in .view-bottombar), then one card: a toolbar (search, Active / Discharged,
   the house, Needs attention), a column head and a .table-row per resident
   sharing --cols. Below 1000px Bed and In stay fold under the name as a facts
   line; on a phone each row stacks (name | band, facts with the flags) and
   opens in place on a tap, showing two equal buttons.
   ══════════════════════════════════════════════════════ */
.roster-page {
  min-width: 0;
}

.roster-pending-btn {
  gap: 6px;
}

.roster-card {
  --cols: minmax(0, 1.3fr) 200px 100px 130px minmax(0, 1.4fr) 160px;
  min-width: 0;
  padding: 0;
  overflow: hidden;
}

/* The toolbar: search, the tabs as a segmented filter, the house, Needs
   attention. Heights come from the controls' own classes and the one
   (pointer: coarse) block; nothing here sizes a control by width. */
.roster-search {
  display: flex;
  flex: 0 1 240px;
  align-items: center;
  gap: 8px;
  min-width: 0;
  min-height: var(--control-h);
  padding: 0 10px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  background-color: var(--bg-input);
  color: var(--text-muted);
}

.roster-search:focus-within {
  border-color: var(--color-brand);
}

.roster-search svg {
  flex: none;
}

.roster-search-input {
  flex: 1 1 0;
  min-width: 0;
  padding: 0;
  border: 0;
  background: transparent;
  font-size: 13px;
  color: var(--text-primary);
  outline: none;
  appearance: none;
}

.roster-search-input::placeholder {
  color: var(--text-muted);
}

.roster-tabs {
  min-height: var(--control-h);
}

.roster-tabs .segmented-option {
  font-size: 13px;
}

.roster-house-select {
  flex: none;
  width: auto;
  max-width: 220px;
  min-height: var(--control-h);
  padding: 0 10px;
  font-size: 13px;
}

.roster-attention-btn,
.roster-clear-btn {
  white-space: nowrap;
}

.roster-clear-btn {
  flex: none;
  min-height: var(--control-h);
  padding: 0 10px;
  font-size: 13px;
}

.roster-attention-btn[aria-pressed="true"] {
  border-color: var(--color-brand);
  background-color: var(--color-brand-glow);
}

.roster-colhead,
.roster-row {
  grid-template-columns: var(--cols);
}

.roster-row {
  font-size: 13px;
}

.roster-cell-name {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

/* A long name wraps rather than clips: a name on a record is read whole. */
.roster-client-name {
  font-size: 14px;
  font-weight: 600;
  overflow-wrap: anywhere;
  color: var(--text-primary);
}

/* The facts line folds Bed and In stay under the name below 1000px; the
   desktop row has them in their own columns. */
.roster-row-facts {
  display: none;
  font-size: 12px;
  overflow-wrap: anywhere;
  color: var(--text-secondary);
}

.roster-fragments {
  display: none;
}

.roster-fragment.is-warning {
  font-weight: 600;
  color: var(--text-warning);
}

.roster-cell-bed,
.roster-cell-stay {
  overflow-wrap: anywhere;
  color: var(--text-secondary);
}

.roster-cell-stay {
  font-variant-numeric: tabular-nums;
}

.roster-cell-flags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 6px;
  min-width: 0;
}

/* Phase 13: the one standing chip sits after the band chip in the same cell. */
.roster-cell-band [data-discipline-standing] {
  margin-left: 6px;
}

/* The flag slots are spans; an empty one takes no room. */
.roster-cell-flags > span:empty {
  display: none;
}

.roster-dash {
  color: var(--text-muted);
}

/* The dash says nothing is waiting: it goes as soon as a flag is drawn,
   including the Out / Back flag that is filled in after the read lands. */
.roster-cell-flags:has(.chip) .roster-flags-none {
  display: none;
}

.roster-left-date {
  white-space: nowrap;
  color: var(--text-secondary);
}

.roster-cell-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
}

.roster-log-btn,
.readmit-client-btn {
  white-space: nowrap;
}

/* The open-the-record button is a chevron in the table; its words show only
   in an open phone row. */
.roster-open-label {
  display: none;
}

.roster-note {
  margin: 0;
  /* The hairline above the note spans the card; .view-footnote's 75ch measure
     stopped it at 45% of the width. */
  max-width: none;
  padding: 10px 14px;
  border-top: 1px solid var(--border-color-light);
}

.roster-empty p {
  margin: 0 0 4px;
}

/* The admit dialog: 460px, the fields two across, the name and the bed over
   both, Cancel then Admit resident in the footer. A phone gets the sheet every
   dialog is, one field per line. */
.modal-container[data-admit-dialog] {
  --modal-pad: 20px;
  max-width: 460px;
  padding: 20px 24px;
}

.modal-container[data-admit-dialog] .modal-header {
  margin-bottom: 6px;
}

.modal-container[data-admit-dialog] .modal-footer {
  margin-top: 16px;
}

.admit-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
  align-items: start;
}

.admit-form .form-group {
  margin: 0;
}

.admit-form-note,
.admit-span {
  grid-column: 1 / -1;
}

.admit-form-note {
  margin: 0 0 4px;
  font-size: 12px;
  color: var(--text-secondary);
}

.roster-form-pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
}

/* [hidden] restated: each class here sets its own display. */
.roster-page[hidden],
.roster-search[hidden],
.roster-cell-name[hidden],
.roster-cell-flags[hidden],
.roster-cell-actions[hidden],
.admit-form[hidden],
.roster-form-pair[hidden] {
  display: none !important;
}

/* Tablet landscape (style guide §1): the same columns, a little narrower. */
@media (max-width: 1199px) {
  .roster-card {
    --cols: minmax(0, 1.2fr) 180px 90px 120px minmax(0, 1.2fr) 170px;
  }
}

/* Tablet portrait (< 1000px): Bed and In stay fold under the name. The search
   box takes the first toolbar line, so the tabs, house, Needs attention and
   Clear filters wrap together on the second instead of one dropping alone. */
@media (max-width: 999px) {
  .roster-search {
    flex: 1 1 100%;
  }

  .roster-card {
    --cols: minmax(0, 1.3fr) 120px minmax(0, 1.2fr) 170px;
  }

  .roster-col-bed,
  .roster-col-stay,
  .roster-cell-bed,
  .roster-cell-stay {
    display: none;
  }

  .roster-row-facts {
    display: block;
  }
}

/* Tablet portrait only: the flags column shows when they left, so the facts
   line leaves its copy out. On a phone the column is hidden and the line is the
   only place. */
@media (min-width: 768px) and (max-width: 999px) {
  .roster-fact-left {
    display: none;
  }
}

/* Phone (style guide §5): the toolbar wraps (search, then the tabs, then the
   house and Needs attention sharing a line); a row stacks as name | band, the
   facts line with the flags in their tone, and opens in place on a tap with
   its verb and Open record as two equal buttons. One open at a time. The
   closed row keeps its arrow, so the record is still one tap away (rule 2:
   behaviour the design removes is kept); opening the row moves that same
   button into the action line as Open record. */
@media (max-width: 767px) {
  .roster-search,
  .roster-tabs {
    flex: 1 1 100%;
  }

  .roster-tabs .segmented-option {
    flex: 1 1 0;
  }

  .roster-house-select,
  .roster-attention-btn {
    flex: 1 1 0;
    max-width: none;
    justify-content: center;
  }

  .roster-row {
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: 4px 10px;
    padding: 12px 14px;
    cursor: pointer;
  }

  .roster-row.is-open {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  /* The brand edge is an inset shadow, so the row keeps its padding. */
  .roster-row.is-open {
    background-color: var(--color-brand-glow);
    box-shadow: inset 3px 0 0 var(--color-brand);
  }

  .roster-cell-name {
    display: contents;
  }

  .roster-client-name {
    grid-column: 1;
    grid-row: 1;
    font-size: 15px;
  }

  .roster-cell-band {
    grid-column: 2;
    grid-row: 1;
  }

  .roster-row-facts {
    grid-column: 1 / -1;
    grid-row: 2;
    font-size: 13px;
  }

  .roster-fragments {
    display: inline;
  }

  .roster-cell-flags {
    display: none;
  }

  /* Closed: only the arrow shows, at the end of the first line. */
  .roster-row:not(.is-open) .roster-cell-actions {
    display: contents;
  }

  .roster-row:not(.is-open) .roster-cell-actions > :not([data-open-resident]) {
    display: none;
  }

  .roster-row:not(.is-open) .roster-open-btn {
    grid-column: 3;
    grid-row: 1;
  }

  /* Two equal buttons: a two-track grid, stretched to the taller one. */
  .roster-row.is-open .roster-cell-actions {
    display: grid;
    grid-column: 1 / -1;
    grid-row: 3;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: stretch;
    gap: 8px;
    padding-top: 6px;
  }

  .roster-row.is-open .roster-cell-actions > button {
    margin: 0;
    justify-content: center;
    gap: 4px;
    border: 1px solid var(--border-color);
    background-color: var(--bg-card);
    font-size: 14px;
    font-weight: 500;
    color: var(--text-primary);
  }

  .roster-row.is-open .roster-open-label {
    display: inline;
  }

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

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

.btn-icon-label {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  font-weight: 500;
  padding: 4px 10px;
  /* The style guide's bordered row button (§3); the name is historical. */
  min-height: var(--control-row-h);
  border-radius: var(--border-radius-md);
  background-color: var(--bg-card);
  border: 1px solid var(--border-color);
  transition: background-color var(--transition-fast), border-color var(--transition-fast);
}

.btn-icon-label:hover {
  background-color: var(--bg-card-hover);
  border-color: var(--text-muted);
}

/* ── The row menu (Phase 11 X2) ──────────────────────────────────────────────
   A row shows at most two actions: its main one and "More", which opens the
   rest in labelled groups (src/js/components/rowMenu.js). Its OWN classes:
   .menu-group, .menu-item and .menu-group-heading are the sidebar's.

   Below .btn-icon-label on purpose: the trigger and the items carry that class
   too (tests and handlers select the items by their old classes), and an
   override has to sit after what it overrides at the same specificity. Single
   classes only, so the 44px floor in the (pointer: coarse) block still wins.
   Every class here that sets display restates [hidden]. */
.row-menu {
  position: relative;
  display: inline-flex;
}

.row-menu[hidden] {
  display: none !important;
}

.row-menu-list {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 220px;
  max-width: calc(100vw - 32px);
  padding: 6px;
  background-color: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-lg);
  box-shadow: var(--shadow-lg);
}

.row-menu-list[hidden] {
  display: none !important;
}

.row-menu-group {
  display: flex;
  flex-direction: column;
}

.row-menu-group[hidden] {
  display: none !important;
}

.row-menu-group + .row-menu-group {
  border-top: 1px solid var(--border-color-light);
  padding-top: 4px;
}

/* The same recipe as the sidebar's .menu-group-heading, in the card's colours. */
.row-menu-heading {
  padding: 6px 10px 2px;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.row-menu-item {
  width: 100%;
  justify-content: flex-start;
  text-align: left;
  gap: 8px;
  padding: 8px 10px;
  border-color: transparent;
  background-color: transparent;
  white-space: nowrap;
}

.row-menu-item[hidden] {
  display: none !important;
}

.row-menu-item:hover,
.row-menu-item:focus-visible {
  background-color: var(--bg-card-hover);
  border-color: var(--border-color-light);
}

/* A destructive item (style guide §4.6): last, after the group hairline, in
   the danger colour. A disabled item says why in its own label, muted. */
.row-menu-item.is-danger {
  color: var(--text-danger);
}

.row-menu-item:disabled {
  color: var(--text-muted);
}

@media (max-width: 1199px) {
  .row-menu-list {
    min-width: 240px;
  }
}

/* A phone opens the menu as a bottom sheet: the page dims behind it (the
   wide shadow), and the transparent ::before over the whole screen takes the
   tap that closes it, so that tap never reaches a row underneath. A tap on
   the ::before targets the list itself, which rowMenu.js closes on. 48px
   rows; the compound selector outranks the 44px coarse-pointer floor. */
@media (max-width: 767px) {
  .row-menu-list {
    position: fixed;
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 100;
    min-width: 0;
    max-width: none;
    max-height: 80dvh;
    overflow-y: auto;
    padding: 8px 8px calc(8px + env(safe-area-inset-bottom, 0px));
    border-bottom: none;
    border-radius: var(--border-radius-sheet) var(--border-radius-sheet) 0 0;
    box-shadow: 0 0 0 100vmax var(--bg-overlay);
  }

  .row-menu-list::before {
    content: '';
    position: fixed;
    inset: 0;
    z-index: -1;
  }

  .row-menu-list .row-menu-item {
    min-height: 48px;
    font-size: 14px;
  }
}

.client-config-panel {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--border-color);
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.client-config-panel h5 {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
}

/* v1.43: the Customize rules form's sections, pairs and footer, in place of the
   inline styles it carried. In its own card on Profile the form needs no
   divider above it: the card is the frame. */
.config-section-title {
  margin: 10px 0 4px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border-color-light);
  font-weight: 650;
}

.config-pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 12px;
}

.config-pair.hidden {
  display: none;
}

.config-move-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

.config-rule-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.config-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 4px;
}

.resident-record-config-card .client-config-panel {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

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

.config-req-row {
  display: grid;
  grid-template-columns: 1fr 120px 80px;
  align-items: center;
  gap: 12px;
  background-color: var(--bg-input);
  padding: 8px 12px;
  border-radius: var(--border-radius-sm);
  border: 1px solid var(--border-color-light);
}

.config-req-name {
  font-size: 13px;
  font-weight: 600;
}

.config-req-target {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
}

.config-req-target input {
  width: 45px;
  background-color: var(--bg-input);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-sm);
  padding: 4px;
  text-align: center;
  font-weight: bold;
  font-size: 14px;
  color: var(--text-primary);
}

/* A requirement switched off disables its Target: dim it like a disabled button. */
.config-req-target input:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.config-toggle-switch {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}

/* Custom CSS Toggle Switch */
.switch-input {
  opacity: 0;
  width: 0;
  height: 0;
  position: absolute;
}

.switch-label {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 20px;
  background-color: var(--border-color);
  border-radius: var(--border-radius-full);
  cursor: pointer;
  transition: background-color var(--transition-fast);
}

.switch-label::after {
  content: '';
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background-color: #ffffff;
  top: 2px;
  left: 2px;
  transition: transform var(--transition-fast);
}

.switch-input:checked + .switch-label {
  background-color: var(--color-brand);
}

.switch-input:checked + .switch-label::after {
  transform: translateX(20px);
}

/* ── Toggle rows ─────────────────────────────────────────────────────────────
   A bordered row: what it is on the left, a switch on the right. It replaced
   native checkboxes wearing transform:scale(), which enlarged the PAINT and
   not the hit box — so on every one of them the thing you could see was
   bigger than the thing you could click.

   The whole row is the <label>, so the text is part of the target rather than
   decoration beside it. The switch itself is a <span>.switch-label rather than
   a nested <label> (which would be invalid inside one) and reuses the roster
   drawer's switch, so the app has ONE toggle and not two that drift. */
.toggle-row-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

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

.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  background-color: var(--bg-input-focus);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  cursor: pointer;
  /* The whole row is the tap target; 44 is the floor a finger needs and it is
     kept at every width because these rows are never crowded. */
  min-height: 44px;
}

.toggle-row-label {
  font-size: 12px;
  color: var(--text-secondary);
  user-select: none;
}

.toggle-row-note {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  color: var(--text-muted);
}

.toggle-row-wide {
  margin-bottom: 16px;
}

.modal-home-group {
  margin-top: 10px;
  margin-bottom: 0;
}

/* A single boolean requirement: the tick and its label on one row, on the
   same inset surface the day cells use. */
.boolean-checkbox-row {
  flex-direction: row;
  gap: 12px;
  width: fit-content;
  padding: 8px 12px;
  min-height: 44px;
  background-color: var(--bg-input-focus);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
}

.boolean-checkbox-row .custom-checkbox-ui {
  width: 26px;
  flex-shrink: 0;
}

/* A panel nested INSIDE a card. One step in, never a black wash. */
.inset-panel {
  padding: 12px;
  background-color: var(--bg-input-focus);
}

/* In the log window an OP group is a section on a hairline, not a card inside
   the dialog card. The .schedule-grid columns are unchanged. */
.client-detail-modal .inset-panel {
  background: transparent;
  border: 0;
  border-top: 1px solid var(--border-color-light);
  border-radius: 0;
  box-shadow: none;
  padding: 10px 0;
}

.empty-inset {
  text-align: center;
  padding: 15px;
  background-color: var(--bg-input-focus);
  border: 1px dashed var(--border-color-light);
  border-radius: var(--border-radius-md);
}

/* The work-hours calendar. It used to paint itself the old cyan at 5% with a
   20% cyan border, both hard-coded, so it stayed the PREVIOUS brand colour
   after the palette moved and read as a differently-branded panel. */
.work-schedule-calendar {
  padding: 12px;
  width: 100%;
  box-sizing: border-box;
  background-color: var(--bg-input-focus);
  border-color: var(--border-color);
}

/* In the log window the calendar is part of the requirements list, not a box. */
.client-detail-modal .work-schedule-calendar {
  background: transparent;
  border: 0;
  box-shadow: none;
  border-radius: 0;
  padding: 0;
}

.work-hour-input {
  padding: 4px;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  width: 100%;
  box-sizing: border-box;
}

/* Settings (the Settings handoff): a summary line, four underline tabs
   (Facility · Backup · This station · Errors), and cards inside each tab.
   Every panel is rendered; the inactive ones carry `hidden`. */
.settings-page {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 860px;
}

/* The summary line sits in the page's 12px gap, not its own margin. */
.settings-page > .view-summary {
  margin: 0;
}

.settings-summary-line .is-compliant { color: var(--text-success); }
.settings-summary-line .is-marginal { color: var(--text-warning); }
.settings-summary-line strong {
  font-variant-numeric: tabular-nums;
}

.settings-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.settings-panel[hidden] {
  display: none !important;
}

/* The short tab label exists for a phone only (below). */
.settings-tab-short {
  display: none;
}

.glass-card.settings-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 16px;
  box-shadow: none;
}

.settings-card-head {
  margin: 0;
}

/* The card heading is an 11px uppercase section heading (style guide §2). */
.settings-card-title {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.settings-card-title.is-danger { color: var(--text-danger); }
.settings-card-title.is-brand { color: var(--color-brand); }

.settings-card-sub {
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--text-secondary);
}

.settings-help {
  margin: 0;
  font-size: 11px;
  color: var(--text-muted);
}

.settings-last-group {
  gap: 8px;
  margin-bottom: 0;
}

.settings-file-label {
  cursor: pointer;
}

.settings-row-slider {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.slider-labels {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  font-size: 13px;
}

.slider-labels > span:first-child {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
}

.threshold-num-display {
  font-size: 13px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  text-align: right;
}

/* The band lives on the VALUE the slider moves, not on the label beside it. */
.threshold-num-display.is-compliant { color: var(--text-success); }
.threshold-num-display.is-marginal { color: var(--text-warning); }

/* The band bar: the two thresholds read at a glance. JS sets the widths. */
.settings-band-bar {
  display: flex;
  height: 8px;
  overflow: hidden;
  border-radius: var(--border-radius-sm);
  background-color: var(--bg-input-focus);
}

/* Its own class: .settings-band is the Houses & beds table header, and a
   segment that inherited its padding would show at a 0% width. */
.settings-band-seg.is-red { background-color: rgba(var(--non-compliant-rgb), 0.5); }
.settings-band-seg.is-yellow { background-color: rgba(var(--marginal-rgb), 0.5); }
.settings-band-seg.is-green { background-color: rgba(var(--compliant-rgb), 0.5); }

/* Refill lead time: under a hairline, the label and help left, the box right. */
.settings-refill-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-top: 8px;
  border-top: 1px solid var(--border-color-light);
}

.settings-refill-text {
  min-width: 0;
}

.settings-refill-text label {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
}

.settings-refill-text .settings-help {
  margin-top: 2px;
}

.settings-num-input {
  flex: 0 0 auto;
  width: 72px;
  padding: 4px 8px;
  text-align: center;
  background: var(--bg-input);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
}

.slider-input {
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  height: 6px;
  border-radius: var(--border-radius-full);
  background: var(--border-color);
  outline: none;
}

.slider-input::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  border-radius: var(--border-radius-full);
  background: var(--color-brand);
  cursor: pointer;
  transition: transform var(--transition-fast);
}

.slider-input::-webkit-slider-thumb:hover {
  transform: scale(1.25);
}

/* Firefox renders the thumb unstyled without its own pseudo-element, and a
   ::-webkit-/::-moz- selector LIST is dropped whole by both engines, so these
   are declared as their own rules rather than folded into the ones above. */
.slider-input::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border: none;
  border-radius: var(--border-radius-full);
  background: var(--color-brand);
  cursor: pointer;
  transition: transform var(--transition-fast);
}

.slider-input::-moz-range-thumb:hover {
  transform: scale(1.25);
}

/* A fact list: the label column sized to its words, the value takes the rest. */
.settings-facts {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 6px 16px;
  margin: 0;
  font-size: 13px;
}

.settings-facts dt {
  color: var(--text-secondary);
}

.settings-facts dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

/* This station's Version row (v2.05): the status words, then Update when a
   newer version is out. The button carries `hidden` until then, and a rule that
   sets `display` beats the UA [hidden] rule, so it is restated. */
.settings-facts .settings-version-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
}
.settings-version-row [data-station-update][hidden] {
  display: none;
}

/* The Update automatically switch, under the facts. */
.settings-auto-update {
  margin-top: 12px;
}

.settings-btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Backup: the buttons under a hairline, below the facts. */
.settings-facts + .settings-btn-row {
  padding-top: 10px;
  border-top: 1px solid var(--border-color-light);
}

/* The two marked cards: one migration (brand), one destructive (only its
   heading and its outlined button carry the danger colour). */
.settings-card-brand { border-color: var(--color-brand); }

/* Outlined, never filled (the Settings handoff). Disabled reads muted. */
.settings-danger-btn {
  border-color: var(--color-danger);
  color: var(--text-danger);
  font-weight: 600;
}

.settings-danger-btn:hover {
  border-color: var(--color-danger);
  background-color: rgba(var(--non-compliant-rgb), 0.08);
}

.settings-brand-btn {
  border-color: var(--color-brand);
  color: var(--color-brand);
  font-weight: 600;
}

.settings-brand-btn:hover {
  border-color: var(--color-brand);
  background-color: var(--color-brand-glow);
}

.settings-danger-btn:disabled,
.settings-danger-btn:disabled:hover,
.settings-brand-btn:disabled,
.settings-brand-btn:disabled:hover {
  border-color: var(--border-color);
  color: var(--text-muted);
  background-color: var(--bg-card);
}

/* Settings > Errors: one list card, a head line, rows on a shared grid
   (when · id · route), a footer note. Replaces the old <details>. */
.glass-card.settings-list-card {
  padding: 0;
  overflow: hidden;
  box-shadow: none;
}

.error-log-head {
  gap: 4px 10px;
}

.error-log-title {
  margin: 0;
  font-size: 14px;
  font-weight: 650;
  white-space: nowrap;
}

.error-log-sub {
  margin: 0;
  min-width: 0;
}

.error-log-copy {
  margin-left: auto;
}

.error-log-list {
  --cols: 170px 130px minmax(0, 1fr);
  list-style: none;
  margin: 0;
  padding: 0;
}

.table-row.error-log-row {
  min-height: 36px;
  padding-block: 6px;
}

.error-log-time {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

.error-log-row code {
  font-size: 12px;
  font-weight: 600;
}

.error-log-route {
  overflow-wrap: anywhere;
}

.error-log-empty {
  margin: 0;
  padding: 24px 14px;
}

.error-log-foot {
  margin: 0;
  max-width: none;
  padding: 10px 14px;
  border-top: 1px solid var(--border-color-light);
}

/* Phone (the Settings handoff): the four tabs share the width with a short
   "Station"; buttons span the card; error rows stack. Layout only: control
   heights follow the pointer (the coarse block), never the width. The header
   chip goes: the This station tab says the same thing. */
@media (max-width: 767px) {
  .settings-tabs {
    gap: 0;
    overflow-x: visible;
  }

  .settings-tabs .tab {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
    gap: 4px;
    padding: 10px 4px;
  }

  /* Clipped like .sr-only, not removed: the tab keeps its full name. */
  .settings-tab-full {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }

  .settings-tab-short {
    display: inline;
  }

  .settings-btn-row > .btn-primary,
  .settings-btn-row > .btn-secondary {
    flex: 1 1 100%;
    justify-content: center;
  }

  .form-input#setting-facility-name {
    font-size: 16px;
  }

  .error-log-copy {
    margin-left: 0;
  }

  .table-row.error-log-row {
    grid-template-columns: minmax(0, 1fr) auto;
    padding: 10px 14px;
  }

  .error-log-route {
    grid-column: 1 / -1;
  }
}

/* System Notification Toast Alert */
.toast-notification {
  position: fixed;
  bottom: 24px;
  right: 24px;
  background-color: var(--bg-card);
  color: var(--text-primary);
  border: 1px solid var(--color-brand);
  padding: 12px 24px;
  border-radius: var(--border-radius-md);
  box-shadow: var(--shadow-lg);
  font-size: 14px;
  font-weight: 550;
  z-index: 999;
  display: flex;
  align-items: center;
  gap: 10px;
  animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

/* The toast's KIND, stamped by src/js/toast.js (data-toast-kind).
   One host, one timer, four kinds: success/info read as the brand rule above;
   failure and error take the danger edge, because the whole reason those two
   outrank the others is that the user has to notice them. Colour is the only
   difference — the wording carries the meaning, so a reader who cannot tell the
   two edges apart loses nothing. */
.toast-notification[data-toast-kind="failure"],
.toast-notification[data-toast-kind="error"] {
  border-color: var(--color-danger);
}

/* History Heatmap Analytics */
.history-layout {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* One client's twelve weeks, wrapping.
   This replaced a clients x weeks <table> whose first column was position:sticky
   — on a phone that column slid over the week cells as they scrolled under it.
   There is no horizontal scroller here and nothing is sticky: the client is
   chosen with #history-client-select and the weeks reflow to whatever width
   there is, which is what makes the view work at 390px.

   auto-FIT, not auto-fill, and a 56px floor. Both were MEASURED in the real
   Electron renderer (the repo's own CDP harness), not reasoned:
     390px  -> 4 columns x 3 rows, 61px per cell, no horizontal overflow
     1280px -> 12 columns x 1 row,  64px per cell
     1920px -> 12 columns x 1 row, 117px per cell
   auto-fill would leave the empty tail tracks in place, so at 1920 the twelve
   weeks would sit at their minimum against the left edge with dead space
   beside them; auto-fit collapses the empty tracks and the twelve stretch to
   fill the row. The floor is 56px because the desktop card's content box
   measures ~873px: a 72px floor fits only ten tracks there and a 64px floor
   eleven, either of which orphans the tail weeks onto a second row for no
   reason. 56px fits all twelve on one row on a desktop and still leaves four
   readable columns on a phone. */
.heatmap-week-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(56px, 1fr));
  gap: 10px;
  border: 1px solid var(--border-color-light);
  border-radius: var(--border-radius-lg);
  padding: 16px;
  background-color: var(--bg-input-focus);
}

/* min-width: 0 so a long label can never push a track wider than its share and
   overflow the card — the 390px-audit discipline. */
.heatmap-week {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.heatmap-week-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.heatmap-cell {
  width: 100%;
  height: 38px;
  border-radius: var(--border-radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  color: #ffffff;
  cursor: pointer;
  transition: transform var(--transition-fast);
}

/* A4: a scored cell names its band in words under the score (the score alone
   is not a band: the thresholds are a setting). The cell grows to hold the
   word, and flex-grow keeps every cell in a grid row the same height. */
.heatmap-cell:not(.empty-cell) {
  height: auto;
  min-height: 38px;
  flex-direction: column;
  gap: 1px;
  padding: 4px 2px;
  line-height: 1.15;
  text-align: center;
}

.heatmap-week > .heatmap-cell {
  flex-grow: 1;
}

.heatmap-band {
  font-size: 11px;
  font-weight: 600;
}

.heatmap-cell:hover {
  transform: scale(1.08);
  box-shadow: 0 0 0 1px var(--color-brand);
}

/* Phase 11 (release round 23): the three bands read as the status chips do
   (.chip-success / -warning / -danger above: the tone's tint behind the
   tone's text, mixed over the card ground as one solid colour: on the page
   ground the amber tint measured 4.32:1 in the light theme), so the score and
   the band word reach 4.5:1 in both themes. White on the 0.85 fills measured
   1.94:1 (marginal, light) to 3.23:1 (compliant, dark). The tone border keeps
   the cell's edge. */
.heatmap-cell.compliant-cell {
  color: var(--text-success);
  background-color: color-mix(in srgb, rgb(var(--compliant-rgb)) 12%, var(--bg-card));
  border: 1px solid rgba(var(--compliant-rgb), 1);
}

.heatmap-cell.marginal-cell {
  color: var(--text-warning);
  background-color: color-mix(in srgb, rgb(var(--marginal-rgb)) 12%, var(--bg-card));
  border: 1px solid rgba(var(--marginal-rgb), 1);
}

.heatmap-cell.non-compliant-cell {
  color: var(--text-danger);
  background-color: color-mix(in srgb, rgb(var(--non-compliant-rgb)) 12%, var(--bg-card));
  border: 1px solid rgba(var(--non-compliant-rgb), 1);
}

.heatmap-cell.empty-cell {
  background-color: var(--bg-input);
  border: 1px dashed var(--border-color);
  color: var(--text-muted);
}

.heatmap-cell.unlogged-cell {
  background-color: var(--bg-input);
  border: 1px dashed var(--border-color);
  color: var(--text-muted);
}

/* A week outside the stay opens nothing, so it does not look pressable. */
.heatmap-cell.empty-cell,
.heatmap-cell.empty-cell:hover {
  cursor: default;
  transform: none;
  box-shadow: none;
}

/* ── Compliance: Trend (the archival history handoff) ───────────────────────
   HistoryView.js: a summary line, the facility chart card (CSS bars, one per
   week), and the weeks card: a residents x weeks matrix on desktop and
   tablet, the picker and one resident's tiles on a phone (HistoryView decides
   which by width). Every class here is the Trend tab's own; the shared
   .heatmap-week-grid above stays the resident record's as it was. */
.history-summary {
  margin: 0;
}

.history-summary strong {
  font-variant-numeric: tabular-nums;
}

.history-summary strong.is-compliant { color: var(--text-success); }
.history-summary strong.is-marginal { color: var(--text-warning); }
.history-summary strong.is-non-compliant { color: var(--text-danger); }

/* The card carries no padding: the toolbar, the body and the legend do. */
.glass-card.history-card {
  padding: 0;
}

.history-card-title {
  font-size: 14px;
  font-weight: 650;
  white-space: nowrap;
  color: var(--text-primary);
}

.history-card-sub {
  font-size: 12px;
  color: var(--text-secondary);
}

.history-scope {
  margin-left: auto;
}

.history-scope-short {
  display: none;
}

/* A week label: the words, or the numerals where the column is narrow. */
.week-short {
  display: none;
}

/* The chart: twelve columns, each a value over a bar over its week. The bar
   is 1.3px per point (130px at 100%); the 150px plot holds it and its value. */
.history-chart-body {
  padding: 14px 14px 8px;
}

.history-chart-empty {
  padding: 28px 0;
  font-size: 13px;
  font-style: italic;
  text-align: center;
  color: var(--text-muted);
}

.trend-bars {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 8px;
}

.trend-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.trend-plot {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  width: 100%;
  height: 150px;
}

.trend-value {
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.trend-value.is-compliant { color: var(--text-success); }
.trend-value.is-marginal { color: var(--text-warning); }
.trend-value.is-non-compliant { color: var(--text-danger); }

.trend-bar {
  width: 100%;
  max-width: 40px;
  height: calc(var(--pct) * 1.3px);
  border-radius: var(--border-radius-sm) var(--border-radius-sm) 0 0;
  opacity: 0.75;
}

.trend-col.is-current .trend-bar {
  opacity: 1;
}

.trend-bar.is-compliant { background-color: rgb(var(--compliant-rgb)); }
.trend-bar.is-marginal { background-color: rgb(var(--marginal-rgb)); }
.trend-bar.is-non-compliant { background-color: rgb(var(--non-compliant-rgb)); }

.trend-axis {
  max-width: 100%;
  overflow: hidden;
  font-size: 11px;
  white-space: nowrap;
  color: var(--text-muted);
}

/* The matrix: a name track, twelve week tracks and the chevron's, shared by
   the head and every row. minmax(0, 1fr) so twelve cells always fit: the
   matrix never scrolls sideways. */
.history-matrix {
  --history-cols: minmax(150px, 1fr) repeat(12, minmax(0, 1fr)) 40px;
  padding-bottom: 6px;
}

.history-matrix-head,
.history-matrix-row {
  display: grid;
  grid-template-columns: var(--history-cols);
  align-items: center;
  gap: 6px;
  padding: 0 14px;
}

.history-matrix-head {
  min-height: 32px;
  border-bottom: 1px solid var(--border-color-light);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.history-matrix-week {
  min-width: 0;
  text-align: center;
}

.history-matrix-head .heatmap-week-label {
  display: block;
  font-size: 11px;
  color: inherit;
}

.history-matrix-row {
  min-height: var(--row-h);
  padding-top: 6px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border-color-light);
  transition: background-color var(--transition-fast);
}

.history-matrix-row:last-child {
  border-bottom: none;
}

@media (hover: hover) {
  .history-matrix-row:hover {
    background-color: var(--bg-card-hover);
  }
}

.history-row-who {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.history-row-name {
  overflow: hidden;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--text-primary);
}

.history-row-facts {
  overflow: hidden;
  font-size: 11px;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--text-secondary);
}

/* A matrix cell: the score on the band's tint, no tone border (the brand
   border on hover); an absence is an empty hairline box, not clickable. */
.history-matrix .heatmap-cell,
.history-matrix .heatmap-cell:not(.empty-cell) {
  height: 34px;
  min-height: 34px;
  padding: 0;
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.history-matrix .heatmap-cell:not(.empty-cell) {
  border-color: transparent;
}

.history-matrix .heatmap-cell:hover {
  transform: none;
  box-shadow: none;
}

.history-matrix .heatmap-cell:not(.empty-cell):hover {
  border-color: var(--color-brand);
}

.history-matrix .heatmap-cell.empty-cell {
  border: 1px solid var(--border-color-light);
  background-color: transparent;
  color: transparent;
  cursor: default;
}

/* Two classes: .btn-icon (6px padding) is later in the file. */
.btn-icon.history-open-btn {
  justify-self: end;
  padding: 0;
  border-radius: var(--border-radius-md);
  color: var(--text-secondary);
}

.history-open-btn:hover {
  color: var(--text-primary);
  background-color: var(--bg-input-focus);
}

/* The phone's one resident: the picker on its own line, the name with Open
   record, then the tiles. */
.history-picker-line {
  padding: 10px 14px 0;
}

.history-picker-line #history-client-select {
  width: 100%;
  min-width: 0;
}

.history-phone-resident {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
}

.history-phone-resident .history-row-who {
  flex: 1 1 auto;
}

.history-open-record {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}

.heatmap-week-grid.is-trend {
  margin: 0 14px 12px;
}

.heatmap-week-grid.is-trend .heatmap-week-label {
  font-size: 11px;
}

/* The legend: four swatches, the thresholds from Settings. */
.history-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin: 0;
  max-width: none;
  padding: 10px 14px;
  border-top: 1px solid var(--border-color-light);
}

.history-legend > span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.history-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: var(--border-radius-sm);
}

.history-swatch.is-compliant { background-color: color-mix(in srgb, rgb(var(--compliant-rgb)) 40%, var(--bg-card)); }
.history-swatch.is-marginal { background-color: color-mix(in srgb, rgb(var(--marginal-rgb)) 40%, var(--bg-card)); }
.history-swatch.is-non-compliant { background-color: color-mix(in srgb, rgb(var(--non-compliant-rgb)) 40%, var(--bg-card)); }
.history-swatch.is-empty { border: 1px dashed var(--border-color); }

/* Tablet: taller cells. */
@media (max-width: 1199px) {
  .history-matrix .heatmap-cell,
  .history-matrix .heatmap-cell:not(.empty-cell) {
    height: 40px;
    min-height: 40px;
  }
}

/* Tablet portrait: a narrower name track, tighter gaps, 36px cells. The week
   labels stay in words here; numerals are the phone's (I-12/I-13). */
@media (max-width: 999px) {
  .history-matrix {
    --history-cols: minmax(110px, 1fr) repeat(12, minmax(0, 1fr)) 40px;
  }

  .history-matrix-head,
  .history-matrix-row {
    gap: 4px;
  }

  .history-matrix .heatmap-cell,
  .history-matrix .heatmap-cell:not(.empty-cell) {
    height: 36px;
    min-height: 36px;
  }

  .history-scope {
    margin-left: 0;
  }
}

/* Phone: the scope reads Active / All, the chart is shorter with tighter
   columns and no % sign, and the tiles go three across. */
@media (max-width: 767px) {
  .history-matrix-head .week-long,
  .trend-axis .week-long {
    display: none;
  }

  .history-matrix-head .week-short,
  .trend-axis .week-short {
    display: inline;
  }

  .history-scope-long {
    display: none;
  }

  .history-scope-short {
    display: inline;
  }

  .trend-bars {
    gap: 4px;
  }

  .trend-plot {
    height: 120px;
  }

  .trend-bar {
    height: calc(var(--pct) * 1px);
  }

  .trend-pct {
    display: none;
  }

  .heatmap-week-grid.is-trend {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* Mouse size for the 28px chevron. A (pointer: fine) block, so the 44px
   floor in the one (pointer: coarse) block still reaches it on a touch
   screen. */
@media (pointer: fine) {
  .history-open-btn {
    min-width: var(--control-row-h);
    min-height: var(--control-row-h);
  }
}

/* One state block (Phase 11 X1b): empty, loading, offline, error and no access
   are all this class: a title, one sentence, at most one action. The padding is
   one value in both themes. It sets no colour of its own beyond the two text
   tokens, which the themes already swap. */
.empty-state-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px 20px;
  text-align: center;
}

.empty-state-container[hidden] {
  display: none;
}

.empty-state-icon {
  color: var(--text-muted);
  margin-bottom: 16px;
}

.empty-state-container h4 {
  font-size: var(--fs-h4);
  font-weight: 700;
  margin-bottom: 6px;
}

.empty-state-container p {
  font-size: 13px;
  color: var(--text-secondary);
  max-width: 360px;
}

.empty-state-container > .rent-drawer-actions,
.empty-state-container > .form-actions,
.empty-state-container > button {
  margin-top: 14px;
}

/* Keyframe Animations */
/* Respect a device that has asked for less motion. The flat restyle removed the
   pulse, the hover lift and the checkbox bounce, so what is left is short colour
   and background fades and the one slideIn below — but a staff member with
   vestibular sensitivity should not have to take that on trust. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

@keyframes slideIn {
  from {
    transform: translateY(20px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

/* ══════════════════════════════════════════════════════
   HISTORY BACKFILL MODAL
   ══════════════════════════════════════════════════════ */

/* The card is the SHELL's dialog element since phase 2c, so it carries
   .modal-container as well as this class. Everything .modal-container sets is
   already overridden by the rules below (width, max-width, max-height, overflow)
   — except its 32px padding, and the 20px the <=900px block re-states. This card
   has no padding of its own: the header band, the legend strip and the table
   wrapper each carry theirs. Written as a COMPOUND selector rather than as a
   later bare `.backfill-modal { padding: 0 }` because a media query adds no
   specificity of its own, so a single-class override would have to sit below it
   in the file to win — and would silently stop winning the moment the two moved.
   (0,2,0) beats (0,1,0) wherever either one is written. */
.modal-container.backfill-modal {
  padding: 0;
}

.backfill-modal {
  background: var(--bg-card);
  border: var(--glass-border);
  border-radius: var(--border-radius-lg);
  box-shadow: var(--shadow-lg);
  width: 96vw;
  max-width: 1200px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: slideIn 0.3s ease;
}

.backfill-modal-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: 28px 32px 20px;
  border-bottom: 1px solid var(--border-color-light);
  flex-shrink: 0;
  gap: 16px;
}

/* Pinned like every dialog header. Written as its own rule on purpose: the shared
   sticky block zeroes the header's top padding (--modal-pad is 0 on this card),
   which would flatten the 28px above the title. Position and background only;
   the padding above and the <=999px trim below stay this header's own. */
.modal-container > .backfill-modal-header {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--bg-card);
}

/* Date range controls strip */
.backfill-date-controls {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 14px;
  flex-wrap: wrap;
}

.backfill-date-group {
  display: flex;
  align-items: center;
  gap: 8px;
}

.backfill-date-label {
  display: flex;
  align-items: center;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
}

.backfill-date-input {
  background: var(--bg-input);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-sm);
  color: var(--text-primary);
  font-size: 13px;
  padding: 6px 10px;
  height: 34px;
  transition: border-color var(--transition-fast);
  cursor: pointer;
}

.backfill-date-input:focus {
  outline: none;
  border-color: var(--color-brand);
}

/* Suppress webkit date picker button styles */
.backfill-date-input::-webkit-calendar-picker-indicator {
  opacity: 0.5;
  cursor: pointer;
  filter: invert(0.5);
}

.backfill-week-count {
  font-size: 12px;
  white-space: nowrap;
}

/* Why a start date was refused (outside the stay). Empty when there is none. */
.backfill-date-problem {
  font-size: 12px;
  color: var(--color-danger);
}

/* A derived requirement's stored number: read-only, as in the log window. */
.backfill-derived-value {
  font-weight: 600;
  text-align: center;
}

/* Row that already has data saved — subtle highlight */
.backfill-week-row.backfill-has-data {
  background-color: rgba(var(--compliant-rgb), 0.06);
}

.backfill-title {
  font-size: 20px;
  font-weight: 700;
  display: flex;
  align-items: center;
  color: var(--text-primary);
  margin-bottom: 8px;
}

.backfill-subtitle {
  font-size: 13px;
  color: var(--text-secondary);
  line-height: 1.5;
  max-width: 680px;
}

.backfill-legend {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 32px;
  background-color: var(--bg-input-focus);
  border-bottom: 1px solid var(--border-color-light);
  flex-shrink: 0;
  flex-wrap: wrap;
}

.backfill-legend-item {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
}

.backfill-legend-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}

.backfill-table-wrapper {
  overflow: auto;
  flex: 1;
  padding: 0;
}

.backfill-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.backfill-th {
  padding: 10px 12px;
  text-align: left;
  font-weight: 700;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-secondary);
  background: var(--bg-card);
  border-bottom: 2px solid var(--border-color);
  position: sticky;
  top: 0;
  z-index: 2;
  white-space: nowrap;
}

.backfill-week-th {
  min-width: 180px;
}

.backfill-td {
  padding: 10px 12px;
  vertical-align: middle;
  border-bottom: 1px solid var(--border-color-light);
}

.backfill-week-row:hover {
  background-color: var(--bg-card-hover);
}

.backfill-week-row.backfill-current-week {
  background-color: var(--color-brand-glow);
}

.backfill-week-label {
  min-width: 180px;
}

.backfill-week-name {
  font-size: 12px;
  font-weight: 700;
  color: var(--text-primary);
}

.backfill-week-range {
  font-size: 11px;
  color: var(--text-secondary);
  margin-top: 2px;
}

.backfill-score-wrap {
  margin-top: 6px;
}

.backfill-no-data {
  font-size: 11px;
  color: var(--text-muted);
  font-style: italic;
}

/* Daily checkbox group inside backfill table */
.backfill-daily-group {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
}

.backfill-day-chk {
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
}

.backfill-day-chk input[type="checkbox"] {
  display: none;
}

.backfill-day-ui {
  width: 26px;
  height: 26px;
  border-radius: var(--border-radius-md);
  background-color: var(--bg-input);
  border: 1px solid var(--border-color);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--text-secondary);
  transition: all var(--transition-fast);
  user-select: none;
}

.backfill-day-chk:hover .backfill-day-ui {
  border-color: var(--color-brand);
  color: var(--color-brand);
}

.backfill-day-chk input:checked + .backfill-day-ui {
  background-color: var(--color-brand);
  border-color: var(--color-brand);
  color: var(--color-brand-fg);
}

.backfill-target-info {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  margin-top: 4px;
  text-align: center;
}

/* Count input group in backfill table */
.backfill-count-group {
  display: flex;
  align-items: center;
  gap: 6px;
}

.backfill-counter-btn {
  width: 28px;
  height: 28px;
  border-radius: var(--border-radius-sm);
  background-color: var(--bg-input);
  border: 1px solid var(--border-color);
  font-size: 16px;
  font-weight: bold;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all var(--transition-fast);
  flex-shrink: 0;
}

.backfill-counter-btn:hover {
  background-color: var(--bg-input-focus);
  border-color: var(--color-brand);
  color: var(--color-brand);
}

.backfill-count-input {
  width: 52px;
  height: 28px;
  text-align: center;
  background: var(--bg-input);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-sm);
  font-size: 14px;
  font-weight: 700;
  color: var(--text-primary);
  padding: 0 4px;
  transition: border-color var(--transition-fast);
}

.backfill-count-input:focus {
  outline: none;
  border-color: var(--color-brand);
}

/* Hide number spinners */
.backfill-count-input::-webkit-outer-spin-button,
.backfill-count-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
}

/* Notes cell */
.backfill-notes-td {
  min-width: 180px;
}

.backfill-notes-input {
  width: 100%;
  background: var(--bg-input);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-sm);
  color: var(--text-primary);
  font-size: 12px;
  padding: 6px 8px;
  resize: vertical;
  min-height: 44px;
  transition: border-color var(--transition-fast);
}

.backfill-notes-input:focus {
  outline: none;
  border-color: var(--color-brand);
}

/* Action cell */
.backfill-action-td {
  white-space: nowrap;
}

.backfill-save-week-btn {
  font-size: 12px;
  padding: 6px 12px;
  gap: 4px;
}

.backfill-empty-notice {
  text-align: center;
  padding: 40px;
  color: var(--text-secondary);
  font-style: italic;
}

/* G2-021: EACH WEEK IS A CARD, NOT A ROW OF A 2000px TABLE.
   The table put every requirement, the notes and the Save in one line, so on a
   1280 desktop and on a tablet the Notes box and Save sat past a sideways
   scroll. The markup stays a table (tests and the save code read its cells),
   but every part is a block and each week's row is a grid: the week label on
   top, the requirement cells wrapping as space allows, then Notes and the
   week's Save, each on its own full-width line. Nothing is wider than the
   dialog, so there is nothing to scroll sideways. */
.backfill-table,
.backfill-table tbody {
  display: block;
  width: 100%;
}
.backfill-table thead {
  display: none;
}
.backfill-table .backfill-week-row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 4px 8px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--border-color-light);
}
.backfill-table .backfill-td {
  display: block;
  min-width: 0;
  border-bottom: 0;
  padding: 6px 4px;
}
.backfill-table .backfill-week-label {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 12px;
  min-width: 0;
}
.backfill-table .backfill-week-label .backfill-week-range,
.backfill-table .backfill-week-label .backfill-score-wrap {
  margin-top: 0;
}
.backfill-table .backfill-notes-td,
.backfill-table .backfill-action-td {
  grid-column: 1 / -1;
  min-width: 0;
}
.backfill-table .backfill-action-td {
  text-align: right;
}
/* The requirement's name, which the hidden column head used to carry. */
.backfill-cell-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-secondary);
  margin-bottom: 4px;
}
.backfill-table .backfill-empty-notice {
  display: block;
  border-bottom: 0;
}
/* The one Save for the window lives in the footer. */
.modal-container.backfill-modal > .modal-footer {
  flex-shrink: 0;
  margin: 0;
  padding: 12px 20px;
  border-top: 1px solid var(--border-color-light);
}
.backfill-footer-note:empty {
  display: none;
}
.backfill-footer-note {
  margin-right: auto;
  font-size: 13px;
  color: var(--text-secondary);
}

/* Job Tracker UI Styles */
.job-tracker-container {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 10px;
}

.job-cards-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* A job application is a row on a hairline, not a box, and does not change
   under the pointer. The class is also the handler hook (closest()). */
.job-application-card {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border-color-light);
  border-radius: 0;
  padding: 10px 0;
}

/* Date, Follow up, Call back. On a phone the 1.2fr date column came to about
   120px, and the picker icon covered the year; the date takes a line to itself. */
.job-card-meta {
  display: grid;
  grid-template-columns: 1.2fr 1fr 1fr;
  gap: 8px;
  align-items: center;
}

@media (max-width: 767px) {
  .job-card-meta {
    grid-template-columns: 1fr 1fr;
  }

  .job-card-meta > .form-input {
    grid-column: 1 / -1;
  }
}

.job-checkbox-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
  color: var(--text-secondary);
}

.job-checkbox-label input {
  width: 16px;
  height: 16px;
  accent-color: var(--color-brand);
}

.job-checkbox-label:hover {
  color: var(--text-primary);
}

.btn-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6px;
  border-radius: var(--border-radius-sm);
  transition: all var(--transition-fast);
}

.btn-icon:hover {
  background: var(--bg-card-hover);
}


/* ==========================================================================
   MEDICATION CALENDAR
   ========================================================================== */

/* The calendar owns the full content width; medication management and dose
   logging both live in the day modal. */
.med-day-modal {
  max-width: 820px;
}

/* Doses this day: a band across the dialog, like the resident modal's score
   panel, not a card inside the card. */
.med-day-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px;
  margin-bottom: 16px;
  background: var(--bg-sidebar);
  border-top: 1px solid var(--border-color-light);
  border-bottom: 1px solid var(--border-color-light);
  border-radius: 0;
  box-shadow: none;
}

.med-day-summary[hidden] {
  display: none;
}

.med-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

/* ── The page (the overhaul's Medications handoff) ───────────────────────────
   Today's passes, the resident's summary line, then ONE card: a toolbar with
   the picker, Active / Include discharged and the three view tabs, and under
   it the chosen view (calendar, inventory or the medication list). */
.med-card {
  padding: 0;
}

.med-client-select {
  width: 240px;
  max-width: 100%;
  padding: 6px 10px;
  font-size: 13px;
  background-color: var(--bg-input);
}

.med-view-tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}

/* Underline tabs, the same as the day window's and the record's (.tabs-underline
   .tab draws the chosen one). Only the floor and the tight padding are here. */
.med-view-tabs .med-view-tab {
  min-height: var(--control-row-h);
  padding: 0 10px;
  font-size: 12px;
}

.med-view-count {
  padding: 0 6px;
}

.med-view-panel[hidden] {
  display: none !important;
}

.med-calendar {
  padding: 12px 14px 14px;
}

.med-summary-chip {
  vertical-align: middle;
}

.med-month-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}

.med-month-label {
  min-width: 120px;
  font-size: 14px;
  font-weight: 650;
  text-align: center;
}

.med-month-arrow {
  min-width: var(--control-row-h);
}

.med-month-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.med-filter-label {
  font-size: 12px;
  color: var(--text-secondary);
}

.med-filter-select {
  width: auto;
  max-width: 220px;
  padding: 4px 8px;
  font-size: 12px;
  background-color: var(--bg-input);
}

/* The only 7-column grid in the app */
.med-grid-dowhead,
.med-month-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
}

.med-grid-dowhead {
  margin-bottom: 6px;
}

.med-grid-dowhead span {
  text-align: center;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.med-day-cell {
  /* 68px (the overhaul's Medications handoff); a busy day grows. */
  min-height: 68px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px;
  background: var(--bg-app);
  border: 1px solid var(--border-color-light);
  border-radius: var(--border-radius-md);
  transition: border-color var(--transition-fast);
}

.med-day-cell:hover {
  border-color: var(--border-color);
}

.med-day-cell.out-of-month {
  background: transparent;
  border-color: transparent;
  opacity: 0.35;
}

.med-day-cell.is-today {
  border-color: var(--color-brand);
  background: var(--color-brand-glow);
}

.med-day-cell.is-future {
  opacity: 0.55;
}

.med-day-cell.is-future .med-pip {
  cursor: not-allowed;
}

/* The day number, and the ratio at the right. */
.med-day-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 2px 4px;
}

.med-day-num {
  font-size: 12px;
  font-weight: 700;
  color: var(--text-secondary);
}

.med-day-cell.is-today .med-day-num {
  color: var(--color-brand);
}

.med-day-ratio {
  font-size: var(--fs-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

.med-day-ratio.is-complete {
  /* The readable tint: --color-success (emerald) on a day cell was about 2.2:1. */
  color: var(--text-success);
}

/* A4: complete is a check mark as well as a colour, drawn before the ratio. */
.med-day-cell {
  position: relative;
}

.med-day-ratio.is-complete::before {
  content: "\2713";
  display: inline-block;
  margin-right: 2px;
  font-size: var(--fs-xs);
  line-height: 1;
}

.med-day-none {
  font-size: 11px;
}

/* Compact dose dots: one per scheduled dose that day, status only. Editing
   happens in the day modal, so the grid stays readable at any regimen size. */
.med-day-dots {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
  flex: 1;
  align-content: flex-start;
  min-width: 0;
}

.med-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--border-radius-full);
  border: 1px solid var(--border-color);
  background: transparent;
  flex-shrink: 0;
}

.med-dot.is-taken {
  background: var(--color-success);
  border-color: var(--color-success);
}

/* A4: three SHAPES, not three colours. Taken is a filled circle (above),
   documented missed a cross, not logged a dashed ring. The
   legend draws the same .med-dot, so it mirrors these. */
.med-dot.is-missed {
  position: relative;
  border-color: transparent;
  border-radius: 0;
  background: transparent;
  color: var(--color-danger);
}

.med-dot.is-missed::before {
  content: "\00D7";
  position: absolute;
  inset: -1px;
  font-size: 14px;
  font-weight: 800;
  line-height: 8px;
  text-align: center;
}

.med-dot.is-pending {
  border-style: dashed;
  border-color: var(--text-muted);
  background: transparent;
}

.med-dot-more {
  font-size: var(--fs-xs);
  font-weight: 800;
  color: var(--text-muted);
}

.med-day-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
  margin-top: auto;
  /* R2e: the ratio, the PRN and count chips and the "+" can be wider than a
     cell at any width (at 1280 they ran 33px past the cell, so the "+" sat
     over the next day and a click there opened that day). Wrap, never spill. */
  flex-wrap: wrap;
}

/* R2e: the month-grid PRN and count chips are shared chips, compact in the
   cell (Q7), and allowed to wrap inside a ~34px phone foot. */
.med-day-foot .chip {
  padding: 1px 4px;
  white-space: normal;
  overflow-wrap: anywhere;
  min-width: 0;
  max-width: 100%;
}

/* THE WHOLE CELL IS THE TARGET (the overhaul's Medications handoff): the
   button that opens the day lies over the cell, edge to edge, above its
   content, so a tap anywhere on the day lands on it. It draws nothing of its
   own; the cell's border answers the pointer and the focus ring is the
   button's, the size of the cell. Being the cell's size, it is at least 44px
   wherever the cell is (52px tall and about 45 wide at 390). */
.med-day-open {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: inherit;
  background: transparent;
  cursor: pointer;
}

.med-day-open:focus-visible {
  outline: 2px solid var(--color-brand);
  outline-offset: 1px;
}

/* ---- Day logging modal ---- */
.med-modal-card {
  background: var(--bg-input-focus);
  border: 1px solid var(--border-color-light);
  border-radius: var(--border-radius-md);
  padding: 14px;
}

/* In the day dialog the sections are not boxes: no tint, no border box, no
   radius, a hairline between one section and the next. */
.med-day-modal .med-modal-card {
  background: none;
  border: 0;
  border-top: 1px solid var(--border-color-light);
  border-radius: 0;
  padding: 10px 0 14px;
}

.med-day-modal .med-modal-card:first-child {
  border-top: 0;
  padding-top: 0;
}

.med-modal-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}

.med-modal-name {
  font-size: 15px;
  font-weight: 700;
}

.med-dose-time {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  font-weight: 600;
}

.day-checkbox-label.is-missed .custom-checkbox-ui {
  border-color: var(--color-danger);
  background-image: linear-gradient(135deg, transparent 45%, var(--color-danger) 45%, var(--color-danger) 55%, transparent 55%);
}

.med-missed-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  flex-wrap: wrap;
}

.med-missed-label {
  font-size: 11px;
  font-weight: 700;
  color: var(--color-danger);
}

/* A dose that is not due yet is not a miss. */
.med-missed-label.is-early {
  color: var(--text-secondary);
}

.med-prn-entry {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border-color-light);
  font-size: 12px;
}

.med-prn-time {
  font-weight: 800;
  color: var(--text-secondary);
  white-space: nowrap;
}

.med-prn-note {
  flex: 1;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.med-prn-add {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  align-items: center;
}

.med-prn-add .form-input {
  flex: 1;
}

.med-legend {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid var(--border-color-light);
  font-size: 11px;
  color: var(--text-secondary);
}

.med-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* The month summary is a sentence, not a legend key: as an inline-flex item its
   text and its <strong> wrapped as separate pieces on a phone. */
.med-legend-note {
  display: block;
  margin-left: auto;
  font-size: 11px;
  color: var(--text-muted);
}

.med-legend-swatch {
  width: 12px;
  height: 12px;
  border-radius: 2px;
  background: var(--bg-card);
  border: 1px solid var(--border-color);
}

.med-legend-swatch.is-taken {
  background: var(--color-success);
  border-color: var(--color-success);
}

.med-legend-swatch.is-missed {
  border-color: var(--color-danger);
}

.med-legend-swatch.is-prn {
  border-style: dashed;
}

/* Medication definition cards (mirrors .job-application-card) */
.med-def-card {
  background: var(--bg-input-focus);
  border: 1px solid var(--border-color-light);
  border-radius: var(--border-radius-md);
  padding: 12px;
  margin-bottom: 10px;
  transition: all var(--transition-fast);
}

/* In the day dialog the list is rows on hairlines, with no hover change. */
.med-day-modal .med-def-card {
  background: none;
  border: 0;
  border-top: 1px solid var(--border-color-light);
  border-radius: 0;
  padding: 10px 0;
  margin-bottom: 0;
}

.med-day-modal .tabs-header + .med-def-card {
  border-top: 0;
}

.med-def-card.is-inactive {
  opacity: 0.55;
}

.med-def-name {
  font-size: 14px;
  font-weight: 700;
}

.med-def-meta {
  font-size: 11px;
  color: var(--text-secondary);
  margin-top: 2px;
  overflow-wrap: anywhere;
}

.med-def-actions {
  display: flex;
  gap: 6px;
  margin-top: 8px;
  flex-wrap: wrap;
}

/* Dose time editor rows in the add/edit form */
.med-dose-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 120px 64px 32px;
  gap: 6px;
  align-items: center;
  margin-bottom: 6px;
}

/* Tablet: 72px cells in landscape, 64 in portrait (the handoff). */
@media (max-width: 1199px) {
  .med-day-cell {
    min-height: 72px;
  }
}

@media (max-width: 999px) {
  /* M3-9: reclaim horizontal space per cell — a tighter grid gap and cell
     padding give each of the seven columns more width so the dots, the ratio
     and the PRN/count chips fit without clipping or forcing page overflow.
     The day-of-week header shares the gap so its columns stay aligned. */
  .med-grid-dowhead,
  .med-month-grid {
    gap: 4px;
  }

  .med-day-cell {
    min-height: 64px;
    padding: 4px;
  }
}

/* Phone: 52px cells, 6px dots; the picker takes the toolbar's first line, the
   two-way control and the tabs a line each, the tabs sharing the width. */
@media (max-width: 767px) {
  /* Narrow side padding and a 2px gap keep each of the seven cells, which is
     its day's button, at least 44px wide at 390. */
  .med-calendar {
    padding: 10px 4px 12px;
  }

  .med-grid-dowhead,
  .med-month-grid {
    gap: 2px;
  }

  .med-day-cell {
    min-height: 52px;
  }

  .med-day-dots .med-dot {
    width: 6px;
    height: 6px;
  }

  .med-client-select {
    flex: 1 1 100%;
    width: 100%;
  }

  .med-discharged-seg,
  .med-view-tabs {
    flex: 1 1 100%;
    margin-left: 0;
  }

  .med-discharged-seg .segmented-option,
  .med-view-tab {
    flex: 1 1 0;
    min-width: 0;
  }

  .med-month-tools {
    flex: 1 1 100%;
    margin-left: 0;
  }

  .med-filter-select {
    flex: 1 1 0;
    min-width: 0;
    max-width: none;
  }
}

/* ══════════════════════════════════════════════════════
   MOBILE SIDEBAR DRAWER (M3-1)
   The phone tier (under 768px). The 232px sidebar leaves the grid and becomes
   an off-canvas drawer that the menu button opens over a backdrop. Tablets get
   the icon rail below instead; at 1200px and up none of this applies.
   ══════════════════════════════════════════════════════ */
@media (max-width: 767px) {
  /* The grid collapses to a single column: the main content spans the full
     viewport width because the sidebar is now position:fixed, out of flow. */
  .app-layout {
    grid-template-columns: 1fr;
  }

  /* THE DRAWER SCROLLS ITS OWN MENU. The desktop sidebar keeps `min-height: 0`
     on .sidebar-menu so the row can shrink, but nothing ever gave the menu
     somewhere for the overflow to GO — on the desktop the short-viewport block
     compacts the rows instead, and that block is scoped to `min-width: 901px`
     and so never reaches a drawer. A phone in landscape is ~390px tall: eight
     destinations, four group headings and the footer do not fit, and with no
     overflow rule the surplus was simply clipped by the fixed drawer with no
     scrollbar and no way to reach Sign out.

     -webkit-overflow-scrolling matches what .modal-tabs and the modal container
     already ask for, so the drawer flicks like the rest of the app on iOS. */
  .sidebar-menu {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* The drawer: fixed, full-height, translated fully off the left edge when
     closed so it steals none of the viewport. */
  .app-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: var(--sidebar-width);
    max-width: 85vw;
    z-index: 200;
    /* Off-canvas to the left. A token-width pixel translate rather than -100%:
       max-width:85vw only ever SHRINKS the drawer below 260px, so translating by
       the full --sidebar-width always clears the viewport at any phone width. */
    transform: translateX(calc(-1 * var(--sidebar-width)));
    /* Closed, it is out of the tab order and the accessibility tree too (G1-034).
       The visibility change waits for the slide, so the close still animates. */
    visibility: hidden;
    transition: transform var(--transition-normal), visibility 0s linear 0.25s;
    box-shadow: var(--shadow-lg);
  }

  /* Open state — app.js toggles .sidebar-open on .app-layout — slides it in. */
  .app-layout.sidebar-open .app-sidebar {
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--transition-normal), visibility 0s;
  }

  /* The hamburger appears in the header. */
  .sidebar-toggle {
    display: flex;
  }

  /* The backdrop sits under the open drawer and closes it on click. */
  .app-layout.sidebar-open .sidebar-backdrop {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    background-color: var(--bg-overlay);
    z-index: 150;
  }
}

/* ══════════════════════════════════════════════════════
   TABLET ICON RAIL (style guide §1)
   768-1199px. The sidebar is a 64px column of icons: no facility name, no group
   headings (still read by a screen reader, so they are clipped rather than
   removed), each destination a 44px square whose label is its `title` and its
   accessible name. The footer (backup status, the signed-in user, Log out) has
   no room in 64px, so it is hidden here and the menu button in the header
   opens the whole sidebar over the page, footer included, exactly as the phone
   drawer does. Every rail rule is scoped to a CLOSED layout; the open state is
   the plain sidebar, taken out of the grid.
   The label is a bare text node beside the icon, so font-size: 0 is what hides
   it; the icon keeps its own width and height attributes.
   ══════════════════════════════════════════════════════ */
@media (min-width: 768px) and (max-width: 1199px) {
  .app-layout {
    grid-template-columns: var(--rail-width) minmax(0, 1fr);
  }

  /* The main column stays in the second track when the open sidebar leaves the
     grid; auto-placement would otherwise move it into the 64px one. */
  .app-main {
    grid-column: 2;
  }

  .app-layout:not(.sidebar-open) .app-sidebar {
    padding: 12px 10px;
  }

  .app-layout:not(.sidebar-open) .sidebar-header {
    justify-content: center;
    margin-bottom: 10px;
    padding: 0;
  }

  .app-layout:not(.sidebar-open) .logo-text,
  .app-layout:not(.sidebar-open) .sidebar-footer {
    display: none;
  }

  .app-layout:not(.sidebar-open) .menu-group-heading {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .app-layout:not(.sidebar-open) .menu-item {
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    gap: 0;
    font-size: 0;
  }

  .app-layout:not(.sidebar-open) .menu-icon {
    width: 22px;
    height: 22px;
  }

  /* The rail still scrolls on a short screen; a scrollbar would take a third
     of its width. */
  .app-layout:not(.sidebar-open) .sidebar-menu {
    scrollbar-width: none;
  }

  /* The menu button opens the full sidebar. */
  .sidebar-toggle {
    display: flex;
  }

  .app-layout.sidebar-open .app-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: var(--sidebar-width);
    z-index: 200;
    box-shadow: var(--shadow-lg);
  }

  .app-layout.sidebar-open .sidebar-backdrop {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    background-color: var(--bg-overlay);
    z-index: 150;
  }
}

/* ══════════════════════════════════════════════════════
   MOBILE CLIENT DETAIL MODAL (M3-3)
   Reuses the app's existing 900px breakpoint, so at >=900px none of this applies
   and the desktop modal is byte-identical. Below it the 4-tab, ~40-field editor
   in .modal-container reflows for a phone:
     • the four tab buttons (white-space:nowrap) become their OWN horizontal
       scroller instead of spilling out and forcing the modal to scroll sideways
       to reach the fourth tab;
     • the 32px padding is trimmed to reclaim width;
     • the height becomes dvh-aware (vh fallback first) so mobile browser chrome
       does not clip it, still scrolling INTERNALLY via the base overflow-y:auto;
     • the multi-column report/schedule grids stack to one column so every field
       stays reachable rather than being squeezed off the edge.
   ══════════════════════════════════════════════════════ */
@media (max-width: 999px) {
  .modal-container {
    /* dvh-aware, vh first as the fallback for engines without dvh. The base rule
       already sets overflow-y:auto, so the container scrolls internally. */
    max-height: 85vh;
    max-height: 85dvh;
    --modal-pad: 20px;
  }

  /* The tab strip is the scroller: its nowrap buttons scroll WITHIN the strip
     rather than widening the modal. This is what keeps the container from
     scrolling sideways at 390px.

     AND IT NOW SAYS SO. A silent scroller is the reason the fourth tab was
     unreachable in practice rather than in principle: the strip ends flush at
     the modal's padding edge, there is no scrollbar on a touch engine, and a
     clipped-but-not-faded label reads as the end of the list. The four
     backgrounds below are the standard scroll-shadow pair — two `local` covers
     in the card colour that sit ON the content and scroll away from an edge as
     that edge is reached, and two `scroll` shadows pinned to the viewport of the
     strip that they uncover. The net effect is a fade that appears only on the
     side there is more to see, and disappears at each end. A strip with nothing
     to scroll shows neither, so this costs a non-overflowing modal nothing.

     background-color is set alongside, not before: the covers are painted in
     --bg-card so they blend into the modal behind them in either theme. */
  .modal-tabs {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    background-color: var(--bg-card);
    background-image:
      linear-gradient(to right, var(--bg-card) 40%, rgba(0, 0, 0, 0)),
      linear-gradient(to left, var(--bg-card) 40%, rgba(0, 0, 0, 0)),
      linear-gradient(to right, var(--border-color), rgba(0, 0, 0, 0)),
      linear-gradient(to left, var(--border-color), rgba(0, 0, 0, 0));
    background-position: left center, right center, left center, right center;
    background-size: 28px 100%, 28px 100%, 12px 100%, 12px 100%;
    background-repeat: no-repeat;
    background-attachment: local, local, scroll, scroll;
  }

  /* Keep each label on one line and let the strip — not the modal — do the
     scrolling. */
  .modal-tab-btn {
    flex-shrink: 0;
  }

  /* Field grids collapse to a single column so nothing is pushed off-screen.
     !important because the component sets grid-template-columns inline on several
     of these grids, and an inline value otherwise outranks this sheet. */
  .report-grid,
  .schedule-grid {
    grid-template-columns: 1fr !important;
  }
}

/* ══════════════════════════════════════════════════════
   EVERY DIALOG IS A BOTTOM SHEET ON A PHONE (style guide §1, §6)
   One place, because every dialog is the one shell (modal.js mountShell ->
   .modal-container): confirm, prompt, the count check and every
   contentDialog form. Full width from the bottom edge, 12px top corners, a
   grab handle, inputs at 16px so iOS does not zoom into a field, and the
   footer stacked at 44px with the primary on top. The footer is written
   Cancel-then-primary, so column-reverse is what puts the primary first.
   Below the M3-3 block above, which it partly overrides (max-height).
   ══════════════════════════════════════════════════════ */
@media (max-width: 767px) {
  .modal-backdrop {
    align-items: flex-end;
  }

  .modal-container {
    width: 100%;
    max-width: none;
    max-height: 92vh;
    max-height: 92dvh;
    border-bottom: none;
    border-radius: var(--border-radius-sheet) var(--border-radius-sheet) 0 0;
    padding-bottom: calc(var(--modal-pad) + env(safe-area-inset-bottom, 0px));
  }

  /* The grab handle: a cue that this is a sheet, not a control. Pinned to
     the sheet's top edge rather than placed in the flow, so a dialog that
     sets --modal-pad: 0 (the resident record, History, backfill) still shows
     it inside the sheet. */
  .modal-container::before {
    content: '';
    position: absolute;
    top: 6px;
    left: 50%;
    width: 36px;
    height: 4px;
    transform: translateX(-50%);
    border-radius: var(--border-radius-full);
    background-color: var(--border-color);
  }

  .modal-footer {
    flex-direction: column-reverse;
    align-items: stretch;
  }

  .modal-footer > button {
    justify-content: center;
    min-height: 44px;
  }

  /* W14: the log window groups Discard and Save logs in .modal-footer-actions.
     On a phone the group dissolves so they stack with the rest of the footer. */
  .modal-footer > .modal-footer-actions {
    display: contents;
  }

  .modal-footer-actions > button {
    justify-content: center;
    min-height: 44px;
  }

  /* :is() takes its most specific argument, so this outranks a form's own
     field rule (.rent-form .form-input) for selects and textareas too. */
  .modal-container :is(input:not([type="checkbox"]):not([type="radio"]), select, textarea) {
    font-size: 16px;
  }
}

/* ══════════════════════════════════════════════════════
   MOBILE TOUCH TARGETS (M3-4) — WCAG 2.5.5
   Reuses the app's 900px breakpoint. Below it the small tappable controls are
   grown to the 44×44 CSS-px minimum a finger needs; at >=900px none of this
   applies and the desktop sizes (32/36/28px) are byte-identical. Each rule sizes
   the TAP TARGET — the button that receives the pointer event — not the
   decorative glyph inside it.

   NOT here: the medications day-modal dose toggle (.day-checkbox-label). Its
   wrapping label already measures 62×76 at 390px (an inline min-width:62px plus
   the stacked glyph/time), so it is a compliant tap target as-is — and the same
   class is reused by the client-detail daily-requirement day boxes, where a
   44px floor would push that 7-across row wider than the phone modal (the M3-3
   surface). It is left untouched by design.
   ══════════════════════════════════════════════════════ */
@media (max-width: 999px) {
  /* Week navigator arrows — the 32×32 round buttons in the header pill.
     flex-shrink:0 so the crowded header row cannot squeeze them back under 44. */
  .nav-arrow-btn {
    width: 44px;
    height: 44px;
    flex-shrink: 0;
  }

  /* Every date-control pill (the header week, Work today's day, the Transport
     Runs day) grows with those arrows: 44 inside plus the 1px border above and
     below (border-box), so its overflow: hidden cuts nothing off an arrow or
     its focus ring with a mouse either. The same 46 the (pointer: coarse)
     block sets. In the same block as the arrows, so the two always switch at
     the same width. */
  .week-navigator {
    height: 46px;
  }

  /* Counter/stepper +/− buttons in the client-detail count requirement (36×36)
     and the history-backfill count cells (28×28). The value field between them
     keeps its own width; only the buttons grow. */
  .counter-btn,
  .backfill-counter-btn {
    width: 44px;
    height: 44px;
  }
}

/* ══════════════════════════════════════════════════════
   MOBILE INNER-OVERFLOW FIXES (M3-11)
   Reuses the app's 900px breakpoint, so at >=900px none of this applies and the
   desktop layout is byte-identical. Two space-between flex rows whose non-shrinking
   child pushed their min-content wider than the phone box — the document did NOT
   scroll (an ancestor clips overflow-x) so the excess hid inside, taking a control
   off-screen.
   ══════════════════════════════════════════════════════ */
@media (max-width: 999px) {
  /* Issue B — the history-backfill modal header. Its flex:1 left block (> div)
     kept min-width:auto and refused to shrink, so its min-content (~412px) pushed
     #backfill-close-btn ~30px past the 96vw modal's right edge. Trim the fixed
     28/32px padding and let the left block shrink; the close button returns
     on-screen. */
  .backfill-modal-header {
    padding: 16px;
  }
  .backfill-modal-header > div {
    min-width: 0;
  }

  /* Issue A — the medications month .section-header (shared class). At >=900px this
     rule does not apply, so other .section-header uses are untouched; below it the
     header may wrap and #med-client-select (inline min-width:220px) may shrink, so
     the ~353px min-content clears the ~318px box instead of clipping ~35px. */
  .section-header {
    flex-wrap: wrap;
    gap: 8px;
  }
  /* The history heatmap's client picker is the same control with the same inline
     min-width:220px, sitting in the same shared .section-header, so it needs the
     same permission to shrink or it clips at 390px exactly as the medication
     one did. */
  #med-client-select,
  #history-client-select {
    min-width: 0;
  }
}

/* ══════════════════════════════════════════════════════
   MOBILE HEADER — unified across every view
   Below 1000px (phones and tablet portrait). At 1000px and up the desktop header is
   unchanged. Below it EVERY view gets the same shape so the app feels
   consistent page to page:
     Row 1:  [menu] [title + subtitle] ················· [theme toggle]
     Row 2:  [ ◀   date selector (centered pill)   ▶ ]   (dashboard/history only)
   header-actions groups the week-navigator + theme toggle on desktop; on mobile
   it becomes `display: contents`, dissolving the wrapper so those two children
   join the header's own flex flow and can split across the two rows — the theme
   toggle up on row 1 (right edge), the date pill on its own full-width row 2.
   On views with no week (roster/requirements/settings) the pill is display:none,
   so row 2 collapses and the header is just row 1 — same layout, one row shorter.
   (The forced Card view + hidden view toggle live in DashboardView.js.)
   ══════════════════════════════════════════════════════ */
@media (max-width: 999px) {
  .app-header {
    flex-wrap: wrap;
    align-items: center;       /* menu, title line and theme toggle sit level */
    row-gap: 10px;
    column-gap: 12px;
    padding: 12px 16px;
  }

  /* Dissolve BOTH wrappers so the menu, title, theme toggle, subtitle and date
     pill are all direct flex items of the header — then `order` + a 100% basis
     lay them onto the rows above regardless of their DOM nesting. */
  .header-actions,
  .header-title-section {
    display: contents;
  }

  .sidebar-toggle { order: 0; }

  /* Back: beside the menu (order 0, then DOM order), chevron only, so row 1
     still holds the menu, Back, the title, the guide and the theme toggle at
     390px. Its label still names the destination for a screen reader. */
  .back-btn {
    order: 0;
    width: 44px;
    height: 44px;
    padding: 0;
    margin-right: 0;
    justify-content: center;
  }

  .back-btn-label {
    display: none;
  }

  /* Title: next to the menu, growing to push the theme toggle to the right edge.
     min-width:0 lets a long title shrink rather than force a sideways scroll. */
  /* A zero basis, not auto: with Back on screen (v1.52) "Resident roster" at
     its full width plus four 44px buttons overfilled row 1 at 390px under a
     coarse pointer, and the theme toggle wrapped alone onto row 2. From zero
     the title takes only the room left and wraps its own words instead. */
  #view-title {
    order: 1;
    flex: 1 1 0;
    min-width: 0;
    /* A1: one step down the scale here, so the title, the menu, the guide and
       the theme toggle still share row 1 at 390px (at --fs-h1 the toggle
       wrapped under the menu). 20 / 16 keeps the page's h1/h3 ratio at 1.25. */
    font-size: var(--fs-h2);
  }

  /* Guide and theme toggle: same row, right edge, level with the title.
     BOTH need an explicit order. .header-actions is `display: contents` here,
     so its children are laid out by the header's own `order` values — and a
     child without one defaults to order 0, which would put the `?` ahead of
     the hamburger at the far left rather than beside the theme toggle. Sharing
     order 2 is deliberate: equal orders fall back to DOM order, so the pair
     stays in the sequence index.html declares them in. */
  #help-btn,
  #theme-toggle {
    order: 2;
    flex: 0 0 auto;
  }

  /* The view's header actions (F3): a flex item of the header here (its parent
     is `display: contents`), on a full-width row of their own under the
     subtitle. Order 3 is the subtitle's, and equal orders fall back to DOM
     order, which puts the slot after it and before the date pill (order 4).
     NOT on row 1 beside the guide: the title's flex basis is its content
     width, so a long title plus a filled slot overfills row 1 and the header
     wraps there, which stranded the theme toggle alone at the start of row 2
     (measured at 390px on "Resident roster"). A row of its own is the same
     place on every view whatever the title's length. The :empty / [hidden]
     rule above still applies (higher specificity), so an empty slot adds no
     row and no gap. */
  .view-header-actions {
    order: 3;
    flex: 0 0 100%;
    flex-wrap: wrap;
  }

  /* Subtitle: its own full-width row directly under the title (row-gap owns the
     spacing, so drop the desktop margin). */
  #view-subtitle {
    order: 3;
    flex: 0 0 100%;
    margin-top: 0;
  }

  /* Date selector: full-width row below — a pill with the label centered and the
     prev/next arrows pinned to each end. Hidden (display:none, set per-view in
     app.js) on views without a week, so its row simply collapses there. Scoped
     to the header: the same pill inside a page (Work today, Transport Runs)
     is not a flex item of the header and keeps its own width. */
  .app-header .week-navigator {
    order: 4;
    flex: 0 0 100%;
    justify-content: space-between;
  }
}

/* ══════════════════════════════════════════════════════
   THE PAGE'S BOTTOM BAR (style guide §1, phone)
   A view wraps its one primary action in .view-bottombar where it renders
   it, in the summary line. Above 767px the wrapper is display: contents, so
   the button sits in the summary line as if unwrapped. On a phone the wrapper
   is a bar pinned to the bottom of the screen, the button fills it at 44px,
   and the page gets 88px of room underneath so the bar never covers the last
   row. Pure CSS: a repaint re-renders the wrapper with the view, and no node
   is moved, so every listener on the button stays where the view bound it.
   z-index 80 keeps it under the guide panel (90), every dialog (100) and the
   open drawer (150/200).
   ══════════════════════════════════════════════════════ */
.view-bottombar {
  display: contents;
}

@media (max-width: 767px) {
  .view-bottombar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 80;
    display: flex;
    gap: 8px;
    margin: 0;
    padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
    background: var(--bg-sidebar);
    border-top: 1px solid var(--border-color-light);
  }

  .view-bottombar > * {
    flex: 1 1 0;
    min-height: 44px;
    justify-content: center;
    margin: 0;
  }

  .view-content:has(.view-bottombar:not([data-aside])) {
    padding-bottom: 88px;
  }

  /* A form or drawer open on the page has its own primary: the bar steps
     aside so there is one (and so the bar cannot blank the form). */
  .view-bottombar[data-aside] {
    display: none;
  }
}

/* One primary per screen, on every tier: Invite staff… is for People only.
   The button stays in the DOM. */
.admin-dashboard:not([data-admin-active-tab="people"]) .admin-invite-bar {
  display: none;
}

/* Phone only, not every touch screen: typing in the roster search with the
   keyboard up (G2-124). The toolbar's other lines and the pinned Admit bar
   step aside so results show above the keyboard; they come back when the box
   loses focus. */
@media (max-width: 767px) {
  .roster-toolbar:has(.roster-search-input:focus) > :not(.roster-search),
  .view-content:has(.roster-search-input:focus) .view-bottombar {
    display: none;
  }
}

/* [hidden] restated: .view-bottombar sets its own display. */
.view-bottombar[hidden] {
  display: none !important;
}

/* ---- Pill counts ---- */
.med-count-card {
  background: var(--bg-input-focus);
  border: 1px solid var(--border-color-light);
  border-left: 3px solid var(--border-color);
  border-radius: var(--border-radius-md);
  padding: 14px;
}

.med-count-card.is-ok    { border-left-color: var(--color-success); }
.med-count-card.is-short { border-left-color: var(--color-danger); }
.med-count-card.is-over  { border-left-color: var(--color-warning); }

/* In the day dialog a count is a section on a hairline. The 3px tone edge stays
   (and the tone is also in words), padded in as .med-inventory-row.is-low is. */
.med-day-modal .med-count-card {
  background: none;
  border: 0;
  border-top: 1px solid var(--border-color-light);
  border-left: 3px solid var(--border-color);
  border-radius: 0;
  padding: 10px 0 14px 10px;
}

.med-day-modal .med-count-card:first-child {
  border-top: 0;
}

.med-day-modal .med-count-card.is-ok    { border-left-color: var(--color-success); }
.med-day-modal .med-count-card.is-short { border-left-color: var(--color-danger); }
.med-day-modal .med-count-card.is-over  { border-left-color: var(--color-warning); }

.med-count-math {
  font-size: 12px;
  color: var(--text-secondary);
  margin-bottom: 10px;
  line-height: 1.5;
}

.med-count-entry {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: 10px;
  align-items: end;
}

.med-count-entry .form-input {
  padding: 8px 10px;
  font-size: 13px;
}

.med-count-entry .btn-primary {
  /* The border keeps it level with the 1px-bordered count field beside it. */
  border: 1px solid transparent;
  padding: 8px 16px;
  font-size: 13px;
  white-space: nowrap;
}

.med-count-feedback {
  margin-top: 10px;
  font-size: 12px;
  font-weight: 600;
  padding: 8px 10px;
  border-radius: var(--border-radius-sm);
}

.med-count-feedback.is-ok {
  color: var(--color-success);
  background: rgba(var(--compliant-rgb), 0.1);
}

.med-count-feedback.is-short {
  color: var(--color-danger);
  background: rgba(var(--non-compliant-rgb), 0.12);
}

.med-count-feedback.is-over {
  color: var(--color-warning);
  background: rgba(var(--marginal-rgb), 0.12);
}

/* M4-6: offline on the shared backend, the pill-count entry is disabled and this
   reason is shown in its place. Reuses the danger tint the "short" feedback uses. */
.med-count-offline {
  margin-top: 10px;
  font-size: 12px;
  font-weight: 600;
  padding: 8px 10px;
  border-radius: var(--border-radius-sm);
  color: var(--color-danger);
  background: rgba(var(--non-compliant-rgb), 0.12);
}

.med-count-explain {
  margin-top: 10px;
}

.med-count-explain label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
  display: block;
  margin-bottom: 4px;
}

.med-count-result {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.med-count-big {
  font-size: 26px;
  font-weight: 850;
}

.med-count-note {
  font-size: 12px;
  color: var(--text-secondary);
  font-style: italic;
  padding: 8px 10px;
  background: var(--bg-input);
  border-radius: var(--border-radius-sm);
  margin-top: 8px;
  overflow-wrap: anywhere;
}

.med-count-history {
  margin-top: 10px;
  font-size: 12px;
}

.med-count-history summary {
  cursor: pointer;
  color: var(--text-secondary);
  font-weight: 600;
}

.med-count-hist-row {
  display: grid;
  grid-template-columns: 100px minmax(0, 1fr) auto;
  gap: 8px;
  padding: 5px 0;
  border-bottom: 1px solid var(--border-color-light);
  color: var(--text-secondary);
}

/* ── Inventory: where the pills are, and when they run out ─────────────────
   A table in the card's Inventory view (the overhaul's Medications handoff):
   Medication · Kept in · On hand · Runs out · Count…, the head and every row
   sharing --cols. Every colour comes from an existing token. Each cell WRAPS
   rather than scrolls: a long name or chip breaks inside its track. */
.med-inventory-panel {
  --cols: minmax(0, 1.3fr) minmax(0, 1fr) minmax(0, 1.1fr) minmax(0, 1fr) auto;
}

.med-inventory-row {
  grid-template-areas: "med loc stock out act";
  padding-top: 8px;
  padding-bottom: 8px;
  font-size: 12px;
  color: var(--text-secondary);
  /* A long medication name must break rather than push the row wide. */
  overflow-wrap: anywhere;
}

.med-inventory-med { grid-area: med; }
.med-inventory-loc { grid-area: loc; }
.med-inventory-stock { grid-area: stock; }
.med-inventory-out { grid-area: out; }
.med-inventory-row > .table-row-actions { grid-area: act; }

.med-inventory-med {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.med-inventory-loc,
.med-inventory-stock,
.med-inventory-out {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 6px;
}

.med-inventory-stock {
  font-variant-numeric: tabular-nums;
}

.med-inventory-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-primary);
}

.med-inventory-sched {
  font-size: 11px;
  color: var(--text-secondary);
}

.med-inventory-onhand {
  font-weight: 600;
  color: var(--text-primary);
}

.med-inventory-days {
  font-weight: 600;
}

/* The verdict chips may wrap inside their track. */
.med-inventory-out .chip {
  white-space: normal;
}

.med-inventory-note {
  padding: 0 14px 12px;
}

/* The refill flag is a danger chip (R2e). */
.med-refill-flag {
  margin-left: 0;
}

/* On a med-def card the flag sits inline with the name, not pushed to the edge. */
.med-def-name .med-refill-flag {
  margin-left: 6px;
  vertical-align: middle;
}

/* A row that needs a refill is tinted. */
.med-inventory-row.is-low {
  background-color: rgba(var(--non-compliant-rgb), 0.04);
}

/* ── The Medications view of the card: the page's medication list ─────────
   A row per medication: name, kind chip and any discontinued or refill chip,
   then the facts line; Edit… and ⋯ at the right. Discontinued and archived
   rows are faded. The form opens in this view, padded like the rows. */
.med-list-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 0;
}

.med-list-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  min-width: 0;
}

.med-list-name {
  max-width: 100%;
  /* A long name wraps, as Inventory's does, so it can be read in full. */
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  overflow-wrap: anywhere;
}

.med-list-dosage {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
}

.med-list-facts {
  overflow-wrap: anywhere;
}

.med-list-row.is-discontinued,
.med-list-row.is-inactive {
  opacity: 0.65;
}

.med-list-foot {
  padding: 6px 8px;
  border-top: 1px solid var(--border-color-light);
}

.med-list-archived {
  border-top: 1px solid var(--border-color-light);
}

.med-page-panel > .section-header,
.med-page-panel > form,
.med-page-panel > .med-panel-notice {
  margin-left: 14px;
  margin-right: 14px;
}

.med-page-panel > .section-header {
  padding-top: 14px;
}

.med-page-panel > form {
  padding-bottom: 14px;
}

.med-page-panel > .med-panel-notice {
  margin-top: 12px;
}

/* A one-shot line above the medication list — e.g. "record the starting count". */
.med-panel-notice {
  margin-bottom: 12px;
  font-size: 12px;
  font-weight: 600;
  padding: 8px 10px;
  border-radius: var(--border-radius-sm);
  color: var(--color-warning);
  background: rgba(var(--marginal-rgb), 0.12);
}

/* Type and Counted in. On a phone each takes the full row, so the chosen type
   reads in full instead of being cut to half a word. */
.med-type-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 12px;
}

@media (max-width: 640px) {
  .med-type-row {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Storage-location inputs in the medication form. */
.med-loc-row {
  display: grid;
  grid-template-columns: 140px minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
}

.med-loc-row.is-lock {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.med-loc-row label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-secondary);
  display: block;
  margin-bottom: 4px;
}

@media (max-width: 999px) {
  /* One field per line on a phone — three inputs across a 390px screen is how a
     grid blows the page out sideways (the M3-9 pattern). */
  .med-loc-row,
  .med-loc-row.is-lock {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Tablet portrait (the handoff): Kept in folds under the on-hand line and
     Count… takes its own line; the head names three columns. */
  .med-inventory-panel {
    --cols: minmax(0, 1.2fr) minmax(0, 1.3fr) minmax(0, 1fr);
  }

  .med-inventory-row {
    grid-template-areas:
      "med stock out"
      "med loc out"
      "act act act";
    row-gap: 4px;
  }

  .med-inventory-colhead > :nth-child(2),
  .med-inventory-colhead > :nth-child(5) {
    display: none;
  }
}

/* Phone: one column, the verdict beside the name. */
@media (max-width: 767px) {
  .med-inventory-row {
    grid-template-areas:
      "med out"
      "stock stock"
      "loc loc"
      "act act";
  }

  .med-inventory-out {
    justify-content: flex-end;
  }

  .med-inventory-row > .table-row-actions {
    justify-content: flex-start;
  }
}

/* Dose-row header in the medication form (label / time / qty) */
.med-dose-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 120px 64px 32px;
  gap: 6px;
  font-size: var(--fs-xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-muted);
  margin-bottom: 4px;
}

/* ── The direct storage-location affordance and its editor ─────────────────
   The trigger sits inline beside the location badge on an inventory row and on
   a med card. Tokens only, so both themes follow the same swap as the rest of
   the .med-* block. */
.med-loc-trigger {
  flex-shrink: 0;
  color: var(--text-secondary);
}

.med-loc-trigger:hover {
  color: var(--color-brand);
}

/* The empty case has to advertise itself — a pencil beside nothing is invisible
   — so the unset trigger is a labelled, outlined chip rather than a bare icon. */
.med-loc-trigger.is-unset {
  font-size: 11px;
  font-weight: 700;
  padding: 2px 10px;
  border: 1px dashed var(--border-color);
  border-radius: var(--border-radius-full);
  white-space: nowrap;
}

.med-loc-trigger.is-unset:hover {
  border-color: var(--color-brand);
  border-style: solid;
}

/* A narrow dialog: it holds five short fields and nothing else, and a
   full-width modal card would make a two-line task look like a form. */
.med-loc-dialog {
  max-width: 520px;
}

@media (max-width: 999px) {
  /* One field per line on a phone, the same stack .med-loc-row takes. */
  .med-loc-dialog .med-loc-grid {
    grid-template-columns: minmax(0, 1fr) !important;
  }

  /* WCAG 2.5.5: the icon trigger is a ~13px glyph — give it a real tap box.
     It sits in a wrapping flex row, so a 44px box grows the row, never the page. */
  .med-loc-trigger {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .med-loc-trigger.is-unset {
    padding: 2px 14px;
  }
}

/* ══════════════════════════════════════════════════════
   MOBILE TOUCH TARGETS (M3-12) — WCAG 2.5.5, the worst offenders
   Reuses the app's 900px breakpoint, so at >=900px none of this applies and the
   desktop sizes (22/24/29/32px) are byte-identical. Each rule grows the TAP TARGET
   — the element that receives the pointer event — to the 44×44 CSS-px minimum a
   finger needs, without clipping text, overlapping siblings, or re-introducing
   overflow (the med "+" is the delicate one; see its note).
   ══════════════════════════════════════════════════════ */
@media (max-width: 999px) {
  /* Modal header "X" (24×27) on every modal — client-detail #modal-close-btn, the
     med day-modal #med-day-close and the backfill #backfill-close-btn all share
     this class. Center the 24px glyph inside a 44×44 box. */
  .modal-close-btn {
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
  }

  /* Roster + requirements action buttons (about 26px tall). They already wrap below
     900px (M3-7 / M3-10), so a taller row simply grows down. */
  .btn-icon-label {
    min-height: 44px;
  }

  /* The ▲/▼ requirement reorder buttons (32×29). They carry .btn-icon-label so the
     rule above already gives them the 44px height; they are narrow, so add the
     matching min-width. */
  .move-req-btn {
    min-width: 44px;
  }

  /* The month calendar's day button needs nothing here: it lies over its
     whole cell (.med-day-open), so the cell's size is its size. */
}

/* ── M8-1: meeting attendance (the staff surfaces) ─────────────────────────
   Two surfaces are styled here: the per-client occurrence modal, and the
   read-only derived counter that replaces the +/- control in ClientDetailsModal
   for a `verifiedBy: 'meetings'` requirement.

   THE ONE RULE THAT IS NOT COSMETIC: the check-in factor gets NO success
   colouring, NO tick and NO icon — it is deliberately styled exactly like the
   other evidence lines. v1 has no location anchor, so a geotag says a phone was
   somewhere; painting it green would make the interface assert something the
   data cannot support, and staff act on that assertion when they decide credit.
   The dom suite asserts the absence of any success token in that factor's
   markup, so this is a pinned contract, not a preference.

   Every colour comes from an existing token, so both themes follow the same
   swap the rest of the sheet does. Rows WRAP rather than scroll: each item is a
   short standalone phrase, so at 390px they stack into a readable block instead
   of pushing the page sideways. */
.meeting-dialog {
  max-width: 720px;
}

/* v1.46: a plain sentence under the title, like Consents. */
.meeting-evidence-notice {
  font-size: 13px;
  color: var(--text-secondary);
  margin: 0 0 16px;
}

.meeting-hint {
  font-size: 12px;
  color: var(--text-muted);
  margin: 0;
}

.meeting-form h4 {
  font-size: var(--fs-h4);
  font-weight: 700;
  margin: 4px 0 0;
}

/* The shared-backend notice: the fallback form cannot store a record here until
   M8-8 gives the delta protocol a `meetingAttendance` vocabulary. Warning
   coloured, because it explains a control that is greyed out. */
.meeting-form-notice {
  font-size: 12px;
  color: var(--color-warning);
  background: rgba(var(--marginal-rgb), 0.1);
  border: 1px solid var(--color-warning);
  border-radius: var(--border-radius-md);
  padding: 8px 12px;
  margin: 0;
}

.meeting-form input:disabled,
.meeting-form select:disabled,
.meeting-form button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* A field-level complaint, under the box it is about. */
.meeting-field-error {
  font-size: 12px;
  font-weight: 600;
  color: var(--color-danger);
}

.meeting-facilitators {
  margin-bottom: 16px;
}

.meeting-facilitators h4 {
  font-size: var(--fs-h4);
  font-weight: 700;
  margin: 0 0 4px;
}

.meeting-facilitator-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

/* v1.46: the house chip, a tinted fill with no outline. */
.meeting-facilitator {
  font-size: 12px;
  color: var(--text-secondary);
  background: var(--bg-input);
  border-radius: var(--border-radius-full);
  padding: 4px 10px;
  overflow-wrap: anywhere;
}

/* Seen exactly once: the warning tint, the same as every amber chip. It is a
   prompt to ask a question, and the words say so ("seen once"). */
.meeting-facilitator[data-facilitator-once="true"] {
  color: var(--text-warning);
  background-color: rgba(var(--marginal-rgb), 0.12);
}

.meeting-list {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin-bottom: 20px;
}

.meeting-empty {
  font-size: 13px;
  color: var(--text-muted);
  margin: 0;
}

.meeting-row {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border-color-light);
  border-radius: 0;
  padding: 10px 0;
}

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

.meeting-row-main {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.meeting-when {
  font-size: 12px;
  font-weight: 700;
  color: var(--text-secondary);
}

.meeting-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

.meeting-kind {
  font-size: 11px;
  font-weight: 700;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

/* The three factor lines. Identical treatment for all three — see the header. */
.meeting-factors {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 6px;
  font-size: 12px;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.meeting-factor[data-factor-present="false"] {
  color: var(--text-muted);
  font-style: italic;
}

.meeting-row-detail {
  margin-top: 6px;
  font-size: 12px;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

/* ── The read-only derived counter (ClientDetailsModal) ────────────────────
   No input and no +/- buttons exist in this row at all. `completed` for an
   opted-in requirement is `count(granted occurrences in this ISO week)`, written
   by the credit transaction (M8-3) and its local mirror (M8-4) — never by this
   modal. A disabled input would suggest the number is merely locked; there is
   nothing to unlock. */
.meeting-derived-group {
  flex-wrap: wrap;
  gap: 8px 12px;
}

.meeting-derived-value {
  font-size: 14px;
  font-weight: 700;
  color: var(--text-primary);
}

/* ── M8-4: the review queue ────────────────────────────────────────────────
   The evidence panel and the decision are two blocks of ONE row, stacked so a
   reviewer reads what happened and then what to do about it in a single glance.
   The panel is given a quiet left rule rather than a colour: the whole point of
   the graded model is that the system does not grade FOR the reviewer, and a
   green-tinted "verified" panel beside a Grant button is a recommendation drawn
   in CSS. Nothing here colours by status except the badges, which name it. */
.meeting-evidence {
  margin-top: 6px;
  padding-left: 10px;
  border-left: 2px solid var(--border-color-light);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

.meeting-source-marker,
.meeting-facilitator-signal {
  font-size: 12px;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

/* A name seen exactly once. Marked, not accused — it is the shape a made-up
   signature has AND the shape a one-off visit to another group has. */
.meeting-facilitator-signal[data-facilitator-once="true"] {
  color: var(--color-warning);
  font-weight: 700;
}

.meeting-decision {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--border-color-light);
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.meeting-decision-detail {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 12px;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.meeting-decision-who {
  font-weight: 700;
  color: var(--text-primary);
}

.meeting-reason-label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
}

.meeting-reason-input {
  width: 100%;
  max-width: 100%;
  padding: 8px 10px;
  border-radius: var(--border-radius-md);
  border: 1px solid var(--border-color);
  background-color: var(--bg-input);
  color: var(--text-primary);
  font-size: 13px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
}

.meeting-reason-input:focus {
  outline: none;
  border-color: var(--color-brand);
}

/* Deny is NOT painted as the destructive option and Grant is NOT painted as the
   happy path. Either can be the right answer, both are equally reversible, and a
   button pair that reads approve/reject would be the recommendation this surface
   is not allowed to make. Both are plain .btn-icon-label row buttons and take
   that size and shape; there is no rule of their own above the phone one. */
.meeting-decision-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* A row someone else adjudicated while our decision was in flight. Dimmed and
   dashed rather than coloured like an error: nothing went wrong and nothing was
   lost — the record simply moved, and this row is showing a version of it that
   is out of date until a sync brings the real decision in. */
.meeting-row[data-meeting-stale="true"] .meeting-decision {
  opacity: 0.72;
}

.meeting-stale-notice {
  margin: 0;
  padding: 6px 8px;
  border-radius: var(--border-radius-md);
  border: 1px dashed var(--color-warning);
  font-size: 12px;
  font-weight: 700;
  color: var(--color-warning);
}

.meeting-queue-count {
  margin: 8px 0 0;
  font-size: 12px;
  font-weight: 700;
  color: var(--color-warning);
}

/* ── M8-4: the roster's entry points ───────────────────────────────────────
   A badge on the client's own Meetings button, and one facility-wide summary
   above the list. Both are ABSENT at zero — see the render comments. */
/* v1.43: the facility-wide entry is a bordered button in the roster's summary
   line, its count a warning chip. The size, shape, fill and hover are
   .btn-secondary .btn-sm's; no padding or font-size here (at equal specificity
   and below .btn-sm, either would beat the size). */
.roster-pending-btn {
  gap: 6px;
}

@media (max-width: 999px) {
  /* At 390px the occurrence header is four short phrases; let them stack rather
     than force the dialog wide. */
  .meeting-row-main {
    gap: 6px;
  }

  /* The fallback form's date/time controls are the usual iOS-narrow suspects —
     keep them inside the card. */
  .meeting-form input,
  .meeting-form select {
    max-width: 100%;
  }

  /* The decision buttons go full width at phone size: they are the two things a
     reviewer taps, and a 44px target matters more here than a tidy row. */
  .meeting-decision-actions {
    flex-direction: column;
  }

  .meeting-grant-btn,
  .meeting-deny-btn {
    width: 100%;
    justify-content: center;
    min-height: 44px;
  }
}

/* ─────────────────────────────────────────────────────────────────────────────
   Residents & portal access (admin dashboard)

   The rows share the .admin-eval-card row template above (Phase 11 R4g) —
   the two lists sit on the same screen and a resident row that looked
   different would read as a different KIND of thing, which it is not.
   No backdrop-filter here, so no -webkit- sibling is required (the guard in
   test/meta/css-prefix.test.js only fires on that property).
   ───────────────────────────────────────────────────────────────────────────── */

.admin-resident-list {
  margin-bottom: 32px;
}

.admin-resident-name {
  font-weight: 700;
  color: var(--text-primary);
}

.admin-resident-email {
  color: var(--text-secondary);
  word-break: break-all;
}

/* More than one account bound to the same resident — legitimate (disabling is a
   stamp, not a delete), so it is a note rather than a warning colour. */
.admin-resident-extra {
  color: var(--text-secondary);
  font-weight: 600;
}

.admin-resident-orphan-note,
.admin-resident-capped-note {
  font-size: 13px;
  margin: 0 0 12px;
}

/* The typeable invite code, sitting under the link it was parsed out of. */
.admin-code-hint {
  font-size: 13px;
  margin: 0 0 8px;
}

.admin-link-code .admin-link-value {
  letter-spacing: 0.04em;
}

/* The invite code in blocks of five so it can be told apart at a glance. The
   gap is margin, not a character, so copying gives the exact code. */
.admin-link-code .code-group {
  display: inline-block;
  margin-right: 0.7em;
}

.admin-link-code .code-group:last-child {
  margin-right: 0;
}

/* ── v1.50: the Admin page body (the Admin design handoff) ───────────────────
   A summary line in place of six tiles, four tabs in place of five stacked
   sections, Residents and Staff side by side, the notice as live / draft /
   compose. Below every admin rule above, which these override on source order;
   ABOVE the coarse-pointer block, and the only control heights set here are in
   a (pointer: fine) block, so its 44px floors still reach every control.
   Every class here that sets display restates [hidden]. */
.admin-dashboard {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.admin-dashboard > * {
  flex-shrink: 0;
}

.admin-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  font-size: 13px;
  color: var(--text-secondary);
}

.admin-summary strong {
  font-size: 15px;
  color: var(--text-primary);
}

.admin-invite-btn {
  margin-left: auto;
  gap: 6px;
}

.admin-shown-note {
  margin: -6px 0 0;
  font-size: 12px;
}

.admin-tab-pill {
  padding: 1px 6px;
}

.admin-tab-pill[hidden] {
  display: none;
}

.admin-panel[hidden] {
  display: none;
}

/* People: Residents above Staff, one full-width card each. Side by side, the
   13-row Residents list stood beside the 3-row Staff list and left a blank
   block under it. */
.admin-people {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}

.admin-people[hidden] {
  display: none;
}

.admin-people-col {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.admin-eval-list,
.admin-resident-list {
  margin-bottom: 0;
}

/* The head band of a list card: title, "· qualifier", and what sits right. */
.admin-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border-color-light);
}

.admin-card-title {
  margin: 0;
  font-size: 14px;
  font-weight: 650;
  color: var(--text-primary);
}

.admin-card-qual {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-secondary);
}

.admin-card-count {
  margin-left: auto;
  font-size: 12px;
  color: var(--text-secondary);
}

.admin-card-head > .admin-filter,
.admin-card-head > .admin-release-current {
  margin-left: auto;
}

.admin-card-foot {
  margin: 0;
  padding: 10px 16px;
  font-size: 11px;
  color: var(--text-muted);
}

.admin-card-foot[hidden] {
  display: none;
}

.admin-card-foot:empty {
  display: none;
}

.admin-card-foot > p {
  margin: 0;
}

.admin-card-foot > p + p {
  margin-top: 4px;
}

.admin-card-foot .admin-resident-capped-note {
  font-size: 11px;
}

.admin-card-head + .empty-state-container {
  padding: 20px 16px;
}

/* Rows: name 14px/600, the meta line 12px, hover tint. */
.admin-eval-card:hover,
.admin-resident-card:hover {
  background-color: var(--bg-card-hover);
}

.admin-eval-card > .admin-eval-main,
.admin-resident-card > .admin-eval-main {
  gap: 4px 8px;
}

.admin-eval-ident {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 8px;
}

.admin-eval-name,
.admin-resident-name {
  font-size: 14px;
  font-weight: 600;
}

.admin-eval-email {
  font-size: 12px;
}

.admin-eval-meta {
  gap: 6px;
  font-size: 12px;
}

.admin-eval-role {
  font-weight: 600;
  color: var(--text-primary);
}

.admin-eval-you {
  font-weight: 400;
  color: var(--text-muted);
}

.admin-eval-source {
  padding: 2px 8px;
  border-radius: var(--border-radius-sm);
  background-color: var(--bg-input-focus);
  font-size: 11px;
  color: var(--text-secondary);
}

.admin-resident-card .requirement-badge {
  font-variant-numeric: tabular-nums;
}

.admin-resident-email {
  word-break: break-word;
}

/* The single-use link, revealed under its row: a brand-bordered strip. */
.admin-eval-card > .admin-link-reveal,
.admin-resident-card > .admin-link-reveal {
  margin-top: 6px;
  padding: 8px 10px;
  border: 1px solid var(--color-brand);
  border-radius: var(--border-radius-md);
  background-color: var(--color-brand-glow);
}

.admin-link-reveal[hidden] {
  display: none;
}

.admin-link-strip-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}

.admin-link-strip-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-primary);
}

.admin-link-hide {
  margin-left: auto;
  padding: 0 6px;
  border: 0;
  border-radius: var(--border-radius-sm);
  background: transparent;
  font: inherit;
  font-size: 12px;
  color: var(--text-secondary);
  cursor: pointer;
}

.admin-link-hide:hover {
  color: var(--text-primary);
}

.admin-link-reveal .admin-link-warning,
.admin-link-reveal .admin-code-hint {
  margin: 0 0 6px;
  font-size: 12px;
}

.admin-link-reveal .admin-link-row {
  margin-bottom: 6px;
}

.admin-link-reveal .admin-link-row:last-child {
  margin-bottom: 0;
}

/* Feedback: chips, author and screen on line 1 with the time at the right,
   the note under both. */
.admin-feedback-item {
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 4px 12px;
}

.admin-feedback-item[hidden] {
  display: none;
}

.admin-feedback-head {
  gap: 6px 8px;
}

.feedback-author {
  font-size: 13px;
}

.feedback-screen {
  font-family: inherit;
  color: var(--text-secondary);
}

.feedback-at {
  margin-left: 0;
  font-size: 12px;
}

.admin-feedback-note {
  grid-column: 1 / -1;
  max-width: 72ch;
  font-size: 13px;
  line-height: 1.45;
}

/* Release notes: the version column, then the changes. */
.admin-release-item {
  grid-template-columns: 120px minmax(0, 1fr);
  gap: 4px 16px;
  padding: 12px 16px;
  align-items: start;
}

.admin-release-head {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}

.admin-release-version {
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}

.admin-release-date {
  font-size: 12px;
}

.admin-release-this {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-brand);
}

.admin-release-changes {
  max-width: 72ch;
  gap: 4px;
  font-size: 13px;
  color: var(--text-primary);
}

.admin-release-current {
  font-size: 11px;
  font-weight: 600;
  color: var(--color-brand);
}

/* The resident notice: live and draft on the left, the new version right. */
.admin-notice-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(380px, 100%), 1fr));
  gap: 14px;
  align-items: start;
}

.admin-notice-col {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.admin-notice-live,
.admin-notice-draft-card,
.admin-notice-compose {
  padding: 14px 16px;
  min-width: 0;
}

.admin-notice-draft-card {
  border: 1px solid var(--color-warning);
}

.admin-notice-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
}

.admin-notice-version {
  font-size: 14px;
  font-weight: 650;
  color: var(--text-primary);
}

.admin-notice-when {
  margin-left: auto;
  font-size: 12px;
}

.admin-notice-count {
  margin: 12px 0 0;
  font-size: 13px;
  color: var(--text-secondary);
}

.admin-notice-count strong {
  margin-right: 4px;
  font-size: 20px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
}

.admin-notice-live .admin-notice-state {
  margin: 12px 0 0;
}

.admin-notice-current {
  margin-top: 12px;
}

.admin-notice-draft-body {
  margin-top: 12px;
  max-height: 160px;
}

.admin-notice-draft-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  margin-top: 12px;
}

.admin-notice-draft-foot > p {
  flex: 1 1 220px;
  margin: 0;
  font-size: 12px;
}

.admin-notice-compose .admin-notice-label {
  margin: 0;
  font-size: 14px;
  font-weight: 650;
  color: var(--text-primary);
}

.admin-notice-hint {
  margin: 2px 0 10px;
  font-size: 12px;
}

.admin-notice-compose .admin-notice-textarea {
  min-height: 200px;
  line-height: 1.5;
}

.admin-notice-compose .admin-notice-actions {
  justify-content: flex-end;
  margin-top: 10px;
}

/* The handoff's mouse sizes, for a FINE pointer only (the Rent drawer's
   pattern): a plain min-height here would beat the coarse block's floor. */
@media (pointer: fine) {
  .admin-link-hide {
    min-height: 24px;
  }
}

/* The short tab labels exist for a phone only (below). */
.admin-tab-short {
  display: none;
}

/* Phone (the Admin handoff's tablet and phone section). Layout only: every
   control's height still follows the pointer (the coarse block's floors, the
   fine block above), never the width. Tablet needs nothing here: the People
   and notice grids drop to one column on their own below ~1000px. */
@media (max-width: 767px) {
  .admin-release-item {
    grid-template-columns: minmax(0, 1fr);
  }
  .admin-release-head {
    flex-direction: row;
    align-items: baseline;
    gap: 8px;
  }
  .admin-invite-btn {
    margin-left: 0;
  }

  /* The four tabs share the width, with short labels, and never scroll. */
  .admin-tabs {
    gap: 0;
    overflow-x: visible;
  }
  .admin-tabs .tab {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
    gap: 4px;
    padding: 10px 4px;
  }
  .admin-tabs .admin-tab-pill {
    padding: 1px 4px;
  }
  /* Clipped like .sr-only, not removed: the tab keeps its full name. */
  .admin-tab-full {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
  }
  .admin-tab-short {
    display: inline;
  }

  /* Card heads wrap: the filter takes its own full-width line. */
  .admin-card-head > .admin-filter {
    flex: 1 1 100%;
    margin-left: 0;
  }
  /* The segmented well, full width, the options sharing it. */
  .admin-filter {
    display: flex;
  }
  .admin-filter .segmented-option {
    flex: 1 1 0;
    min-width: 0;
  }

  /* Feedback: the time drops to its own line under the chips. */
  .admin-feedback-item {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Activate and Save go full width. */
  .admin-notice-draft-foot > #admin-notice-activate,
  .admin-notice-compose .admin-notice-actions > #admin-notice-save {
    flex: 1 1 100%;
    justify-content: center;
  }
}

/* The medication pass window (Phase 11 Z4, MedPassWindow.js): a dialog holding
   one list card per resident and one .list-row per dose. Below .list-card,
   .list-row, .chip and .form-input, so these win on source order. ABOVE the
   coarse-pointer block on purpose, and nothing here sets a control's height:
   Given, Not given, Undo and Try again are the small button size and the
   reason select is a text field, so that block's 44px floors reach all five
   unchanged. */
.med-pass-date {
  margin-top: 4px;
  font-size: 13px;
}

.med-pass-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
  font-size: 13px;
}

.med-pass-summary[hidden] {
  display: none;
}

.med-pass-totals {
  font-weight: 600;
}

/* "As of 8:01 AM on this device": when the rows were last read from the record. */
.med-pass-asof {
  margin-left: auto;
  color: var(--text-secondary);
  font-size: 12px;
}

.med-pass-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.med-pass-list[hidden] {
  display: none;
}

/* A resident's block. A dialog has no .glass-card behind it, so the block
   carries its own hairline; the name heading is the identity check. */
.med-pass-resident {
  border: 1px solid var(--border-color-light);
  border-radius: var(--border-radius-lg);
}

.med-pass-head {
  flex-wrap: wrap;
}

.med-pass-name {
  min-width: 0;
  margin: 0;
  font-size: var(--fs-h3);
  font-weight: 650;
}

.med-pass-med {
  font-weight: 600;
}

.med-pass-detail {
  color: var(--text-secondary);
  font-size: 12px;
}

.med-pass-status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  margin-top: 4px;
  color: var(--text-secondary);
  font-size: 12px;
}

.med-pass-status[hidden] {
  display: none;
}

/* "Saving" and "Not saved": a row's save state, plain text, never a chip. */
.med-pass-save {
  font-weight: 600;
}

.med-pass-save.is-failed {
  color: var(--text-danger);
}

/* G3-039: kept on this device, waiting for the connection. Not an error. */
.med-pass-save.is-queued {
  color: var(--text-secondary);
}

/* Where "Not saved" has no Try again: the sentence that points at the app's
   own reload or sign-in notice. */
.med-pass-pointer {
  max-width: 280px;
  color: var(--text-secondary);
  font-size: 12px;
  text-align: right;
}

/* The reason select, at the small button's size beside Given. */
.med-pass-reason {
  padding: 4px 8px;
  font-size: 12px;
}

/* A row whose save is in flight keeps its action focusable (the focus stays on
   the row), so it is marked aria-disabled rather than disabled. */
.med-pass-actions button[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Done with doses not saved: the count, Try again and Close anyway. */
.med-pass-unsaved {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
  padding: 10px 12px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  color: var(--text-danger);
  font-size: 13px;
  font-weight: 600;
}

.med-pass-unsaved[hidden] {
  display: none;
}

/* A pass in a list of today's passes (Phase 11 Z5: the Today home's pass
   headings and the Medications strip, one renderer): the time, residents and
   doses, the three count chips, and Open pass. A .list-row; Open pass is the
   small secondary button, so the coarse-pointer floor reaches it unchanged. */
.med-pass-entry-time {
  font-weight: 650;
}

.med-pass-entry-totals {
  color: var(--text-secondary);
  font-size: 12px;
}

.med-pass-entry .list-row-chips {
  margin-top: 4px;
}

/* The strip above the Medications picker: the passes three across (the
   overhaul's Medications handoff), one column on a phone. Each pass is a cell
   that draws its right and bottom hairline; the card's overflow: hidden trims
   the ones on the outer edge. The Today home's pass headings are not in here. */
.med-pass-strip {
  margin-bottom: 16px;
}

.med-pass-strip-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: 0 -1px -1px 0;
}

.med-pass-strip .med-pass-entry {
  align-items: start;
  min-height: 56px;
  padding: 10px 14px;
  border-right: 1px solid var(--border-color-light);
  border-bottom: 1px solid var(--border-color-light);
}

.med-pass-strip .med-pass-entry-time {
  font-size: 15px;
  font-variant-numeric: tabular-nums;
}

/* Zero is silent: a count chip at zero stays in the markup (tests and the
   guide read its data-count) and is hidden, as on the Today home. */
.med-pass-strip .med-pass-entry [data-pass-entry-count][data-count="0"] {
  display: none;
}

@media (max-width: 1199px) {
  .med-pass-strip .med-pass-entry {
    min-height: 64px;
  }
}

@media (max-width: 767px) {
  .med-pass-strip-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.med-pass-strip-title {
  margin: 0;
  font-size: var(--fs-h3);
  font-weight: 650;
}

/* On Today a resident's row sits under its pass heading, stepped in. */
.today-pass-client {
  padding-left: 28px;
}

/* On a phone a dose row stacks: the dose, then its actions on their own line. */
@media (max-width: 767px) {
  .med-pass-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .med-pass-row > .med-pass-actions {
    justify-content: flex-start;
  }

  .med-pass-pointer {
    max-width: none;
    text-align: left;
  }

  .med-pass-asof {
    margin-left: 0;
  }
}

/* ══════════════════════════════════════════════════════
   TOUCH TARGETS ON A COARSE POINTER (audit §6 "Staff app")
   WCAG 2.5.5, everywhere a finger is the pointer — not only on a narrow screen.

   WHAT WAS WRONG. Every 44px rule this sheet has is scoped `max-width: 900px`,
   which is a proxy for "phone" and a bad one. The device a facility actually
   puts on a med cart is a landscape tablet at 1024 or 1280 CSS px: it is driven
   entirely by finger, it matches none of the mobile blocks, and it therefore got
   the MOUSE sizes — .btn-icon-label at about 26px tall, .counter-btn at 30x30,
   .header-icon-btn at 34x34, .tab-btn at ~37px. Those are the controls that
   discharge a resident, change a pill count and switch the roster tab.

   `(pointer: coarse)` asks the question the width was standing in for: is the
   PRIMARY pointing device imprecise? A mouse-driven desktop is `fine` and is
   untouched at every width, so the existing desktop sizes are byte-identical
   there and nothing in the (min-width: 901px) and (max-height: 700px) compact
   block is affected — that block is about a SHORT mouse-driven laptop and this
   one cannot both apply and matter, since it only ever grows a control a mouse
   user never sees grow.

   min-height/min-width rather than height/width, because min-* wins over an
   explicit height in the used-value clamp. That is what lets these four rules
   override `.counter-btn { height: 30px }` and `.header-icon-btn { height:
   34px }` without restating the whole rule or reaching for !important.

   PLACED LAST IN THE SHEET ON PURPOSE. A media query adds NO specificity, so a
   rule inside one only beats a plain rule that appears ABOVE it. All four base
   rules (.header-icon-btn ~L836, .counter-btn ~L2740, .tab-btn ~L3172,
   .btn-icon-label ~L3357) are earlier in this file, and so are the max-width:900px
   blocks that already grow some of them — which this agrees with rather than
   contradicts, since the value is the same 44px.
   ══════════════════════════════════════════════════════ */
@media (pointer: coarse) {
  /* Roster and requirements row actions: Edit, Deactivate, Delete, Consents,
     Discharge, and the requirement reorder arrows. They sit in a wrapping flex
     row, so a taller button grows the row downwards and never the page. */
  .btn-icon-label {
    min-height: 44px;
  }

  /* The row menu's items (Phase 11 X2). They also carry .btn-icon-label, so
     the floor above reaches them; stated here so the item class has its own. */
  .row-menu-item {
    min-height: 44px;
  }

  /* The Rent drawer's text buttons (Receipt, Reverse, Undo import, a note's
     Open) (v1.49). Their mouse sizes are in a (pointer: fine) block beside
     their rules, so nothing below competes with this. The ledger and feedback
     filters are .segmented, floored with the other segmented options. */
  .rent-text-btn {
    min-height: 44px;
  }

  /* The link strip's Hide (v1.50), on the Rent drawer's pattern: mouse sizes
     in a (pointer: fine) block. */
  .admin-link-hide {
    min-height: 44px;
  }

  /* The header's Back (v1.52), on a tablet wide enough to show its word. */
  .back-btn {
    min-height: 44px;
  }

  /* The +/- steppers on a count requirement and on a pill count. Square, so both
     axes are lifted — a 44px-tall, 30px-wide button is still a miss. */
  .counter-btn {
    min-width: 44px;
    min-height: 44px;
  }

  /* The roster/history tab strip. Its padding is vertical-only (10px 2px), so
     the height is what needs the floor; the label supplies the width. */
  .tab-btn {
    min-height: 44px;
  }

  /* The header icons — the guide's "?" and the light/dark toggle. */
  .header-icon-btn {
    min-width: 44px;
    min-height: 44px;
  }

  /* The roster's search box (phone walk, 2026-10-09: 32px on a phone). The
     label is the box the finger hits; the input inside fills it. */
  .roster-search {
    min-height: 44px;
  }

  /* ────────────────────────────────────────────────────────────────────────
     THE SECOND PASS (live pass, 2026-09-18), and why there had to be one.

     The block above picked the right QUESTION — is the primary pointer
     imprecise — and then answered it for four controls. Measured in the live
     app at 1280x551, eleven more sat under the floor, and the two that matter
     most were the ones nobody thinks of as "a control": `.menu-item`, at 29px,
     is the navigation used to reach every screen, and `.btn-primary`, at 31px,
     is the primary action ON every screen. The sign-in form was under it
     entirely — Sign in is 387x31 — which makes the first thing anyone touches
     on a tablet the smallest thing they touch.

     Ordered below the way they were found rather than by importance, so the
     list can be checked against the measurements in the journal entry.
     ──────────────────────────────────────────────────────────────────────── */

  /* The sidebar. A menu item is a full-width flex row, so the floor grows the
     row downwards; eight of them add ~120px to a sidebar that scrolls. */
  .menu-item {
    min-height: 44px;
  }

  /* The three button classes the rest of the app is built from. These carry
     text, so only the height needs a floor — the label supplies the width. */
  .btn-primary,
  .btn-secondary,
  .btn-danger {
    min-height: 44px;
  }

  /* Icon-only, so there is no label to grow it: both axes. */
  .btn-icon,
  .nav-arrow-btn,
  .password-toggle {
    min-width: 44px;
    min-height: 44px;
  }

  /* The date control's pill (Phase 11 X1a) grows with what is inside it, so
     a 44px arrow (or the Transport day input, lifted by .form-input below) is
     not clipped by the pill's overflow: hidden. 46 is 44 inside plus the 1px
     border above and below (border-box). The same floor on all three date
     controls keeps them one height under a finger. */
  .week-navigator {
    min-height: 46px;
  }

  /* The Cards/List segmented control and the full-width disclosure headers
     ("Admit a resident", the requirement form). Both are pressed, neither had
     enough vertical padding to be pressed accurately. */
  .segmented-option,
  .form-card-toggle {
    min-height: 44px;
  }
  /* A one-word option is narrower than a finger: Compliance's List measured
     40px and the house picker's 208 41px on the tablet (2026-10-10). */
  .segmented-option {
    min-width: 44px;
  }

  /* A Trend matrix cell is a button (role="button"): a finger's floor. */
  .history-matrix .heatmap-cell:not(.empty-cell) {
    min-height: 44px;
  }

  /* "Log this week…" on a dashboard card, the one affordance on the card that
     leads to the actual work. It sits in a flex row beside the recovery line,
     so the floor grows the card foot. (.btn-secondary above reaches it too;
     stated here so the card's own button has its floor by name.) */
  .client-card-foot .log-details-btn {
    min-height: 44px;
  }

  /* Text fields, including the two on the sign-in screen. */
  .form-input {
    min-height: 44px;
  }

  /* A checkbox or radio at the UA default is 13x13 — "Include discharged" on
     the medication calendar measured exactly that. 22px is the size the
     consent-category rows already settled on at (max-width: 900px); this is
     the same answer asked of the pointer instead of the width, so the two
     cannot disagree. The BOX is not the target, though — see .filter-check. */
  input[type="checkbox"],
  input[type="radio"] {
    min-width: 22px;
    min-height: 22px;
  }

  /* The label row IS the target: the box alone is a quarter of a fingertip.
     Every label class that wraps a checkbox or radio is listed here
     (test/meta/shape-scale.test.js finds them in the views); each is a flex row,
     so the floor applies. */
  .filter-check,
  .contract-pick,
  .contract-radio,
  .supplies-tick,
  .job-checkbox-label,
  .toggle-row,
  .day-checkbox-label,
  .consent-category,
  .backfill-day-chk,
  .disclosure-picker-option,
  .disclosure-picker-confirm,
  .setup-confirm {
    min-height: 44px;
  }

  /* A compound rule outside this block that sets one of those rows' height
     beats the single-class floor, so it gets its own floor here at the same
     strength. The consent modal's 0,2,0 row sets 32px for its column layout. */
  .consent-form .consent-category {
    min-height: 44px;
  }

  /* The Phase 11 vocabulary's small sizes (26px and 28px with a mouse) and the
     underline tab. Single-class sizes, so this 0,1,0 floor is not beaten by a
     compound; the tab rule matches its base rule's 0,2,0 and is later. */
  .btn-sm {
    min-height: 44px;
  }

  .btn-row {
    min-height: 44px;
    /* 11px text reads small on a 44px finger target (tablet walk 2026-10-09:
       roster Log this week…, Rent Change…); the mouse size stays 11px. */
    font-size: 12px;
  }

  .tabs-underline .tab {
    min-height: 44px;
  }

  /* The resident record page (Phase 11 X4a): the roster row's chevron into
     it, square, so both axes. The page's own buttons (Next resident, Log this
     week) carry the button floors above. */
  .roster-open-btn {
    min-width: 44px;
    min-height: 44px;
  }

  /* The Customize rules panel's weekly-target boxes (Phase 11 X4e: the panel
     moved from the roster row to the record page's Profile tab). Not
     .form-input, so the field floor above does not reach them. */
  .config-target-val {
    min-height: 44px;
  }

  /* Transportation's disclosures (T4: a run's riders, Archived vehicles): the
     summary line is a 44px target. Their base rules, further down, set no
     padding or line-height, so nothing below replaces this. */
  .transport-archived > summary,
  .transport-run-details > summary {
    padding: 12px 0;
    line-height: 20px;
  }

  /* Phase 11 O2: the × in the Out / Back history dialog's title row (the
     row also holds Excuse a night…, which the .btn-secondary floor covers).
     Scoped to that dialog; the other dialogs' × is not that step's. */
  .outing-history-head .modal-close {
    min-width: 44px;
    min-height: 44px;
  }

  /* Phase 11 O3: the x in the other Out / Back dialogs, the ones whose body
     holds the two-column field grid. */
  .modal-container:has(.outing-grid) .modal-close {
    min-width: 44px;
    min-height: 44px;
  }

  /* The Correct dialog's "should not have been made" tick: the label row is
     the target, as .filter-check has it. */
  .outing-grid .supplies-tick {
    min-height: 44px;
  }

  /* Phase 11 R4l: the floors run 2 and the harness baseline left. The
     900px block above grows some of these on a phone; a finger on a 1280
     tablet got the mouse sizes. All are min-* only, so nothing else about
     each control's box changes. */

  /* The x in a dialog title row: .modal-close is the shell default (Consents,
     Meetings, Portal invite, Work schedule, Transportation), .modal-close-btn
     the log window, the day dialog and backfill. The O2/O3 scoped rules above stay; this agrees. */
  .modal-close {
    min-width: 44px;
    min-height: 44px;
  }

  .modal-close-btn {
    min-width: 44px;
    min-height: 44px;
  }

  /* Medications (the overhaul's handoff): the view tabs, and the square
     month arrows and row ⋯, prefixed with .med-card so they beat the
     .btn-icon-only width declared further down. The day button lies over its
     whole cell, so it needs no floor. */
  .med-view-tabs .med-view-tab {
    min-height: 44px;
  }
  .med-card .med-month-arrow,
  .med-card .row-menu-trigger {
    min-width: 44px;
  }

  /* The day window's dose boxes and the weekly log's day boxes: 30px tall
     with a mouse at 1000px and up, which a 1056px tablet is. The label around
     each is taller, but the box is what a finger aims at. */
  .custom-checkbox-ui {
    min-height: 44px;
  }

  /* Admin's ⋯ on each staff row: 28px wide on the tablet (2026-10-10). Two
     classes, so it beats the .btn-icon-only width declared further down. */
  .admin-row-menu-btn.btn-icon-only {
    min-width: 44px;
  }

  /* The text fields in the small dialogs (Consents, Meetings, Portal and staff
     invite): padding 10px plus 14px text is 41-43px. Checkboxes are left
     out: the consent category row is the target, its box stays 22. */
  .invite-form input:not([type="checkbox"]),
  .consent-form input:not([type="checkbox"]),
  .consent-form select,
  .meeting-form input:not([type="checkbox"]),
  .meeting-form select {
    min-height: 44px;
  }

  /* A meeting decision's reason field (35px). */
  .meeting-reason-input {
    min-height: 44px;
  }

  /* The log window's count stepper: the value between the two 44px buttons
     (30px), and its footer's quiet Print link (16px). */
  .counter-value {
    min-height: 44px;
  }

  .modal-footer-link {
    min-height: 44px;
  }

  /* History backfill: the start date (34px), the count steppers and their
     value (28px). The 900px block already squares the steppers on a phone. */
  .backfill-date-input {
    min-height: 44px;
  }

  .backfill-counter-btn {
    min-width: 44px;
    min-height: 44px;
  }

  .backfill-count-input {
    min-height: 44px;
  }

  /* Settings: the refill lead-time number (30px), and the two threshold
     sliders (the Settings handoff: 44px on a touch screen). The slider's box
     grows to 44px; its drawn track stays 6px, painted in the middle. */
  .settings-num-input {
    min-height: 44px;
  }

  .slider-input {
    min-height: 44px;
    background: linear-gradient(var(--border-color), var(--border-color)) center / 100% 6px no-repeat;
  }

  /* Houses & beds (v1.39). Prefixed with .houses-page so these beat the
     screen's own sizes, which are declared further down the sheet. The
     room's ⋯ grows into its own margin so the room head keeps its height. */
  .houses-page .house-room-menu {
    min-width: 44px;
    min-height: 44px;
    margin: -12px -12px -12px auto;
  }
  .houses-page .btn-icon-only {
    min-width: 44px;
  }
  .houses-page .house-population .house-population-select {
    min-height: 44px;
  }
  .houses-page .houses-finish,
  .houses-page .houses-add-house,
  .houses-page .bed-tile,
  .houses-page .house-add-bed,
  .houses-page .house-settings-head .segmented-option,
  .node-menu .node-menu-item {
    min-height: 44px;
  }
  /* A touch phone's bed tile is 48px (the Houses & beds handoff); a mouse
     keeps the desktop tile at every width. */
  @media (max-width: 767px) {
    .houses-page .bed-tile {
      min-height: 48px;
    }
  }

  /* Requirements: Add requirement…, Edit… and the row's ⋯, prefixed with
     .req-page so they beat the sizes declared further down. (The dialog's
     Cancel and Save are .btn-secondary / .btn-primary, floored above.) */
  .req-page .req-add,
  .req-page .req-row-btn {
    min-height: 44px;
  }
  .req-page .req-actions .row-menu-trigger {
    min-width: 44px;
  }

  /* W16 (R9): controls the allow-list above missed, so a finger got the mouse
     size. A disclosure line is padded to 44 the way Transportation's is (a
     padded summary keeps its marker; a flex one loses it). */
  .settings-archived > summary,
  .admin-notice-current > summary,
  .med-count-history > summary,
  .rent-template-help > summary {
    padding: 12px 0;
    line-height: 20px;
  }
  /* The Medications Archived toggle (a quiet text button). */
  .btn-text.is-quiet {
    min-height: 44px;
  }
  /* Icon-only row menus with no screen rule (Transportation's vehicle ⋯, 28px
     wide), the med card's Edit, and the day chips in the Add medication form. */
  .btn-icon-only.row-menu-trigger,
  .med-edit-btn,
  .backfill-day-chk {
    min-width: 44px;
  }
  /* The phone's Trend card draws .is-trend cells at 38px; the Compliance
     matrix cell above is not the same grid. */
  .heatmap-week-grid.is-trend .heatmap-cell:not(.empty-cell) {
    min-height: 44px;
  }
  /* Staff sign-in and set-up fields: 38px with 14px text. The checkbox and
     radio inputs on these screens keep their own size. */
  [data-auth-screen] input:not([type="checkbox"]):not([type="radio"]) {
    min-height: 44px;
  }
}

/* Mouse floor of 24px for the filter options (20px at 12px text). A (pointer:
   fine) block so the 44px floor in the one (pointer: coarse) block still wins
   on a touch screen. */
@media (pointer: fine) {
  .med-discharged-seg .segmented-option,
  .work-today-filter .segmented-option {
    min-height: 24px;
  }
}

/* DELIBERATELY NOT IN THE BLOCK ABOVE: `.auth-link-btn`, the "Set up your
   account" control, which measures 106x16. It is a link-styled button sitting
   INSIDE the sentence "Invited by an administrator? Set up your account", and
   WCAG 2.5.8 exempts a target whose size is constrained by the line-height of
   the text it sits in. Forcing it to 44px would not make it easier to hit; it
   would break the line box of the sentence around it. Recorded here so the
   next person to measure this screen does not read its absence as an oversight
   — which is the mistake this whole second pass exists to correct. */

/* The inline refusal that sits under the field that caused it — the requirements
   library's name box, the discharge dialog's date, the medication end date.
   Every one of them carries `class="form-error hidden"` and is un-hidden by
   removing the class, which is why this rule sets no display of its own: `.hidden`
   is `display: none !important` and must stay the only thing deciding visibility.

   The class was in use before it was ever declared (the discharge dialog shipped
   with it), so a refusal rendered as ordinary body text with nothing about it
   saying it was a refusal. --text-danger, not --color-danger: this is TEXT, and
   the readable tint is the one that carries AA against the card behind it. */
.form-error {
  margin: 6px 0 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-danger);
}

/* ───────────────────────────────────────────────────────────────────────────
   PHASE 5 — resident work schedules, staff side: the roster's Work schedule
   window, the Work Today view and the resident line under each day of the
   weekly hours grid. All three are read-only.

   A LATE change is marked in the warning tone and in words ("Edited late ·
   Sep 21, 2:14 PM"), never by colour alone. A removed-late entry is struck
   through. Every colour is an existing token, so both themes follow.
   ─────────────────────────────────────────────────────────────────────────── */
.work-schedule-dialog {
  max-width: 880px;
}

.work-notice {
  font-size: 12px;
  color: var(--text-muted);
  background: var(--bg-input);
  border-radius: var(--border-radius-md);
  padding: 8px 12px;
  margin: 0 0 16px;
}

.work-empty,
.work-hint {
  font-size: 12px;
  color: var(--text-muted);
  margin: 0;
}

.work-jobs-section,
.work-late-section {
  margin: 0 0 16px;
}

.work-jobs-section h4,
.work-late-section h4 {
  font-size: var(--fs-h4);
  font-weight: 700;
  margin: 0 0 8px;
}

.work-job-list,
.work-job-patterns,
.work-day-entries,
.work-late-list,
.work-late-list ul,
.work-today-entries {
  list-style: none;
  margin: 0;
  padding: 0;
}

.work-job {
  padding: 6px 0;
  border-bottom: 1px solid var(--border-color-light);
}

.work-job-name {
  font-weight: 600;
}

.work-job-position {
  font-weight: 400;
  color: var(--text-secondary);
}

.work-job-patterns li {
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 2px;
}

.work-month-nav {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.work-month-label {
  font-weight: 700;
  min-width: 140px;
  text-align: center;
}

.work-month-today {
  padding: 6px 12px;
  font-size: 12px;
  /* G2-145: a phone squeezed this to 83px and it wrapped to two lines. */
  white-space: nowrap;
  flex-shrink: 0;
}

.work-month-nav {
  flex-wrap: wrap;
}

.work-hours-where {
  font-size: 12px;
  color: var(--text-secondary);
  margin: 0 0 8px;
}

.work-hours-where .work-hours-open-schedule {
  margin-left: 6px;
  padding: 4px 10px;
  font-size: 12px;
}

@media (max-width: 767px) {
  .work-month-label {
    min-width: 0;
    flex: 1;
  }
}

.work-grid-dowhead,
.work-month-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 4px;
}

.work-grid-dowhead {
  margin-bottom: 4px;
}

.work-grid-dowhead span {
  text-align: center;
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.work-day-cell {
  min-height: 64px;
  padding: 4px 6px;
  background: var(--bg-input);
  border: 1px solid var(--border-color-light);
  border-radius: var(--border-radius-md);
  font-size: 11px;
}

.work-day-cell.is-out {
  background: transparent;
  border-color: transparent;
}

.work-day-cell.is-today {
  border-color: var(--color-brand);
}

.work-day-num {
  font-weight: 700;
  color: var(--text-secondary);
}

/* The weekday beside the day number: only where the column head is gone (a
   phone-width list, below). */
.work-day-dow {
  display: none;
  font-weight: 700;
  color: var(--text-secondary);
}

.work-day-entries .work-entry {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 4px;
  margin-top: 2px;
  line-height: 1.3;
}

.work-day-entries .work-entry-employer {
  color: var(--text-secondary);
}

.work-entry-what {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
}

.work-entry-time {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.work-entry.is-off .work-entry-time {
  font-style: italic;
  font-weight: 500;
}

.work-entry.is-removed .work-entry-what {
  text-decoration: line-through;
  opacity: 0.75;
}

/* The late sentence is a warning chip; a chip is nowrap, and this one carries
   a whole sentence and a time, so it wraps inside a phone-width row. */
.work-late-badge {
  white-space: normal;
}

.work-late-item {
  display: flex;
  gap: 10px;
  padding: 4px 0;
  font-size: 13px;
}

.work-late-item .work-entry {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
}

.work-late-date {
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  min-width: 88px;
}

/* A phone-width window: the seven columns become a list of the days that
   have something on them. */
@media (max-width: 767px) {
  .work-grid-dowhead {
    display: none;
  }

  .work-day-dow {
    display: inline;
  }

  .work-month-grid {
    display: block;
  }

  .work-day-cell.is-out,
  .work-day-cell.is-empty {
    display: none;
  }

  .work-day-cell {
    min-height: 0;
    margin-bottom: 4px;
  }
}

/* WORK TODAY (the overhaul's work handoff). The page anatomy: a summary line
   (counts, then the clock at the right), ONE card whose toolbar holds the day
   pill and the segmented filter, a column head, one row per shift or day off,
   and the footnote inside the card. The grid is the card's --cols, shared by
   the head and every row. Control sizes come from the shared small-button,
   record-chevron and segmented classes, so the pointer-coarse floors reach
   them; nothing here sets a control's height. */
.work-today {
  display: flex;
  flex-direction: column;
}

.work-today-clock {
  font-size: 12px;
  white-space: nowrap;
}

.work-today-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

/* The day, between the arrows in the shared date-control pill (Phase 11 X1a):
   the header week label's look (.week-picker-trigger), with no picker under
   it. The size comes from the pill. */
.work-today-date {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 14px;
  font-weight: 600;
  min-width: 180px;
  text-align: center;
  white-space: nowrap;
}

.work-today-filter {
  margin-left: auto;
}

.work-today-card {
  --cols: minmax(150px, 1.2fr) minmax(150px, 1.3fr) minmax(180px, 1.2fr) 150px 170px;
}

.work-today-row {
  padding-top: 8px;
  padding-bottom: 8px;
}

/* A shift on right now: the faintest success wash, so the eye finds it. */
.work-today-row.is-now {
  background-color: rgba(var(--compliant-rgb), 0.04);
}

@media (hover: hover) {
  .work-today-row.is-now:hover {
    background-color: var(--bg-card-hover);
  }
}

.work-today-who,
.work-today-job,
.work-today-shift {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.work-today-bed,
.work-today-employer,
.work-today-position {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.work-today-bed {
  font-size: 11px;
  color: var(--text-secondary);
}

.work-today-position {
  font-size: 12px;
  color: var(--text-secondary);
}

.work-today-entries .work-entry {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  font-size: 13px;
}

.work-today-row .work-entry-time {
  font-weight: 500;
  color: var(--text-secondary);
}

.work-today-row.is-now .work-entry-time {
  color: var(--text-primary);
}

.work-today-began {
  font-size: 11px;
  color: var(--text-secondary);
}

.work-today-dash {
  color: var(--text-muted);
}

/* The 24-hour bar: the day as a 4px track, the shift as its span (success
   when on now, muted when finished, brand otherwise), and on today a 2px tick
   at the current minute. Decoration: the time is in words above it. */
.work-today-bar {
  position: relative;
  display: block;
  height: 4px;
  max-width: 220px;
  margin-top: 4px;
  border-radius: var(--border-radius-full);
  background-color: var(--bg-input-focus);
}

.work-today-bar-span {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: var(--border-radius-full);
  background-color: var(--color-brand);
}

.work-today-bar[data-work-bar="now"] .work-today-bar-span {
  background-color: var(--text-success);
}

.work-today-bar[data-work-bar="finished"] .work-today-bar-span {
  background-color: var(--text-muted);
}

.work-today-bar-now {
  position: absolute;
  top: -2px;
  width: 2px;
  height: 8px;
  margin-left: -1px;
  background-color: var(--text-primary);
}

.work-today-note {
  margin: 0;
  max-width: none;
  padding: 10px 14px;
  border-top: 1px solid var(--border-color-light);
}

/* Tablet portrait (style guide §5): the bar and the column head go, and the
   name and job tracks may shrink to nothing rather than push the row wide. */
@media (max-width: 999px) {
  .work-today-card {
    --cols: minmax(0, 1fr) minmax(0, 1fr) 150px 120px auto;
  }

  .work-today-colhead,
  .work-today-bar {
    display: none;
  }
}

/* Phone: the row stacks (the anatomy's two tracks): name and bed beside the
   status chip, then the job on one line, the shift, and the two actions on a
   line of their own, Work schedule… taking the width. The filter takes the
   toolbar's full width under the day pill. */
@media (max-width: 767px) {
  /* The Today button shares the day pill's row: the pill gives up its 180px
     floor and the row does not wrap, so leaving today does not push the list
     down a row (G3-078). */
  .work-today-nav {
    flex-wrap: nowrap;
    max-width: 100%;
  }

  .work-today-date {
    min-width: 0;
    padding: 0 8px;
  }

  .work-today-filter {
    flex: 1 1 100%;
    margin-left: 0;
  }

  .work-today-filter .segmented-option {
    flex: 1 1 0%;
    min-width: 0;
  }

  .work-today-who {
    grid-column: 1;
    grid-row: 1;
  }

  .work-today-status {
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
  }

  .work-today-job,
  .work-today-shift,
  .work-today-actions {
    grid-column: 1 / -1;
  }

  .work-today-job {
    flex-direction: row;
    gap: 4px;
  }

  .work-today-position::before {
    content: '· ';
  }

  .work-today-actions {
    margin-top: 4px;
  }

  .work-today-open-schedule {
    flex: 1 1 auto;
    justify-content: center;
  }

  .work-today-record {
    margin-left: 0;
    border: 1px solid var(--border-color);
  }
}

/* THE WEEKLY HOURS GRID — the resident's line under the staff box. Smaller and
   quieter than the box, because the box is the staff value being edited. */
.work-resident-line {
  margin-top: 4px;
  font-size: var(--fs-xs);
  line-height: 1.3;
  color: var(--text-muted);
  text-align: left;
  overflow-wrap: anywhere;
}

.work-resident-line.is-late {
  color: var(--text-warning);
}

/* ─────────────────────────────────────────────────────────────────────────────
   RENT & PAYMENTS (phase 7 step 4a) — src/js/components/RentView.js
   The overview is one F1 list card (Phase 11 R4a): .rent-summary is its
   .list-card-head and each resident a .list-row (name and chips, then the
   balance and Open ledger in .list-row-actions; the facts line and the open
   drawer span the row underneath). The drawer is a .card-section inside the
   row, with no card of its own. What else is here is the money: amounts
   right-aligned in tabular figures so a column of balances lines up, and a
   ledger line that stacks on a phone. Below .list-row and .card-section, which
   these rules override.
   ───────────────────────────────────────────────────────────────────────────── */
/* v1.42: the counts, the grace period and the overview's actions on one line
   above the list (the Out / Back summary line), no longer the card's head. */
.rent-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  margin-bottom: 14px;
  font-size: 13px;
  color: var(--text-secondary);
}

.rent-summary strong {
  font-size: 15px;
  color: var(--text-primary);
}

.rent-grace {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.rent-summary-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-left: auto;
}

/* The failed-refresh line sits between the head and the rows, padded like a
   row because the list card has no padding of its own. */
.rent-refresh-error {
  margin: 0;
  padding: 8px 16px;
  border-bottom: 1px solid var(--border-color-light);
  font-size: 13px;
}

/* v1.49 (the Rent handoff): each resident is a card of their own, not a line
   in one list card. Name and chips left, balance and Open ledger right, the
   facts line under both. The open resident is outlined in the brand colour
   and holds its ledger. No overflow clip on these cards: the drawer's More
   menu drops out of the action bar and must not be cut off. */
.rent-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* The Online payments card (RentConnectCard.js) sits under the list like one more
   row: the same 8px gap above it, the rows' 13px text, and 8px between its heading,
   its lines and its button row (it had none: 16px text, every margin 0). */
.rent-connect {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
  font-size: var(--fs-h4);
}

.rent-connect > h4,
.rent-connect > p {
  margin: 0;
}

.rent-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 2px 12px;
  padding: 12px 16px;
  font-size: 13px;
  overflow-wrap: anywhere;
}

.rent-row.is-open {
  display: block;
  padding: 0;
  border: 1px solid var(--color-brand);
  box-shadow: 0 0 0 3px var(--color-brand-glow);
}

.rent-row-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px 12px;
  padding: 14px 16px 12px;
}

.rent-row-main {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  min-width: 0;
}

.rent-row-main[hidden] {
  display: none;
}

.rent-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-primary);
}

.rent-row.is-open .rent-name {
  font-size: 15px;
  font-weight: 650;
}

.rent-row-side {
  display: flex;
  align-items: center;
  gap: 12px;
}

.rent-row > .rent-facts,
.rent-row-head > .rent-facts {
  grid-column: 1 / -1;
}

.rent-owes {
  font-size: 12px;
}

/* The open resident's balance, called out; in the danger colour when behind. */
.rent-row .rent-balance-big {
  font-size: 20px;
  font-weight: 650;
  color: var(--text-primary);
}

.rent-row .rent-balance-big.is-behind {
  color: var(--text-danger);
}

.rent-past-due {
  font-weight: 600;
  color: var(--text-warning);
}

/* The open drawer: stacked sections on hairlines, 18px in from the card edge. */
.rent-drawer {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.rent-drawer[hidden] {
  display: none;
}

.rent-drawer-note {
  margin: 0;
  padding: 0 16px 14px;
}

.rent-drawer h5 {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-secondary);
}

/* The rows break long words anywhere; a button label never breaks mid-word. */
.rent-row button {
  overflow-wrap: normal;
  white-space: nowrap;
}

.rent-drawer-bar {
  align-items: center;
  padding: 0 16px 14px;
}

.rent-more {
  margin-left: auto;
}

.rent-more .row-menu-trigger {
  gap: 6px;
}

.rent-more .row-menu-list {
  min-width: 220px;
  z-index: 20;
}

.row-menu-note {
  margin: 0;
  padding: 2px 10px 6px;
  max-width: 220px;
  font-size: 11px;
  white-space: normal;
}

.rent-menu-danger {
  color: var(--text-danger);
}

/* Reported by the resident: one tinted strip per payment waiting on a decision. */
.rent-reported {
  border-top: 1px solid var(--border-color-light);
}

.rent-report {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding: 10px 16px;
  background-color: rgba(var(--marginal-rgb), 0.07);
}

.rent-report + .rent-report {
  border-top: 1px solid var(--border-color-light);
}

.rent-report > .chip {
  margin-right: 100%;
}

.rent-report-text {
  flex: 1 1 260px;
  margin: 0;
  font-size: 13px;
}

.rent-report-actions {
  display: flex;
  gap: 8px;
  margin-left: auto;
}

/* Unpaid charges beside the rent account; one column when there is no room. */
.rent-drawer-split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  border-top: 1px solid var(--border-color-light);
}

.rent-col {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  padding: 14px 16px;
}

.rent-col-charges {
  border-right: 1px solid var(--border-color-light);
}

.rent-section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 4px 12px;
  font-size: 12px;
}

.rent-charge {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 7px 0;
  border-top: 1px solid var(--border-color-light);
  font-size: 13px;
}

.rent-charge-kind {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
}

.rent-charges-total {
  display: flex;
  justify-content: space-between;
  padding-top: 8px;
  border-top: 1px solid var(--border-color);
  font-size: 13px;
}

.rent-num {
  font-variant-numeric: tabular-nums;
}

.rent-strong {
  font-weight: 600;
  color: var(--text-primary);
}

.rent-account + .rent-account {
  padding-top: 10px;
  border-top: 1px solid var(--border-color-light);
}

.rent-dl {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 6px 16px;
  margin: 8px 0 0;
  font-size: 13px;
}

.rent-dl dt {
  color: var(--text-secondary);
}

.rent-dl dd {
  margin: 0;
}

.rent-note + .rent-note::before {
  content: " · ";
  color: var(--text-muted);
}

/* A text button: a ledger row's Receipt, Reverse and Undo import, a note's
   Open. 24px, no border; the receipt number in the brand colour. */
.rent-text-btn {
  padding: 0 4px;
  border: 0;
  border-radius: var(--border-radius-sm);
  background: transparent;
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
}

.rent-text-btn:hover:not(:disabled) {
  color: var(--text-primary);
  background-color: var(--bg-card-hover);
}

.rent-text-btn:disabled {
  color: var(--text-muted);
  cursor: not-allowed;
}

.rent-text-btn.rent-receipt {
  color: var(--color-brand);
}

/* One look on every tier (DESIGN section 17: pointer changes size, width
   changes layout, never the control type): a ledger row's actions and a
   note's Open are small bordered buttons, as the phone first had them. */
.rent-ledger .rent-line-actions .rent-text-btn,
.rent-notes .rent-text-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0 12px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  background-color: var(--bg-card);
  font-size: 13px;
}

.rent-ledger .rent-line-actions .rent-text-btn:hover:not(:disabled),
.rent-notes .rent-text-btn:hover:not(:disabled) {
  background-color: var(--bg-card-hover);
}

/* The Notes row: label and value share a midline even when a finger-sized
   Open makes the value taller (G6-043). */
.rent-dl-notes {
  align-items: center;
}

.rent-note {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* The ledger: a fixed-column table, newest first. Head and rows share one
   track list; the fixed actions track is what keeps them aligned. Scoped to
   .rent-ledger because the receipt and import dialogs use .rent-line too. */
.rent-ledger {
  padding: 14px 16px 6px;
  border-top: 1px solid var(--border-color-light);
}

.rent-ledger-head {
  justify-content: flex-start;
  margin-bottom: 8px;
}

.rent-filter {
  margin-left: auto;
}

.rent-ledger .rent-line {
  grid-template-columns: 88px minmax(0, 1fr) 84px 84px 130px;
  gap: 12px;
  padding: 8px 0;
  align-items: center;
}

.rent-ledger .rent-line:not(.rent-line-head):hover {
  background-color: var(--bg-card-hover);
}

.rent-ledger .rent-line-head {
  padding: 0 0 6px;
  font-weight: 600;
  letter-spacing: 0.4px;
}

.rent-line-kind {
  font-weight: 500;
}

.rent-line-detail {
  font-size: 12px;
  color: var(--text-muted);
}

.rent-amount.is-credit {
  color: var(--text-success);
}

.rent-line-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 2px;
}

.rent-ledger-none {
  margin: 0;
  padding: 12px 0;
}

.rent-ledger-foot {
  margin: 8px 0;
  font-size: 11px;
  color: var(--text-muted);
}

/* The handoff's mouse sizes for the drawer's text buttons (24px) and the
   ledger filter (26px), for a FINE pointer only. The sheet's one (pointer:
   coarse) block sits above this section, so a plain min-height here would
   beat its 44px floor on source order; with neither rule competing, a finger
   gets the floor and a mouse gets these. */
@media (pointer: fine) {
  .rent-text-btn {
    min-height: 24px;
  }
}

/* "As of", in the header slot beside the overview's actions (Phase 11 X1a). */
.rent-as-of {
  font-size: 12px;
  white-space: nowrap;
}

.rent-balance {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* No margin: the row's gap spaces it from the line above. */
.rent-facts {
  margin: 0;
  font-size: 12px;
}

.rent-lines {
  display: flex;
  flex-direction: column;
  font-size: 13px;
}

.rent-line {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr) 96px 96px;
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border-color-light);
  align-items: baseline;
}

.rent-line-head {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-secondary);
}

.rent-amount {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.rent-late {
  color: var(--text-warning);
}

/* A reversed entry stays in the ledger (it is append-only); it is struck
   through and faded so it is read beside the reversal that took it out. */
.rent-voided {
  opacity: 0.75;
}

.rent-voided .rent-line-entry,
.rent-voided .rent-line-detail,
.rent-voided .rent-amount {
  text-decoration: line-through;
}

/* A reversed credit is no longer money in: not green. */
.rent-voided .rent-amount.is-credit {
  color: var(--text-secondary);
}

/* The collapsed row's chevron is drawn only on a phone (below). */
.rent-open-chevron {
  display: none;
}

/* Tablet (the Rent handoff, v2): a little more room in the ledger's fixed
   tracks and between its rows. Control heights are the pointer's business
   (the one (pointer: coarse) block), never this width's. */
@media (max-width: 1199px) {
  .rent-ledger .rent-line {
    grid-template-columns: 96px minmax(0, 1fr) 90px 90px 160px;
    padding: 10px 0;
  }
  .rent-ledger .rent-line-head {
    padding: 0 0 6px;
  }
  .rent-row {
    padding: 14px 16px;
  }
  .rent-row.is-open {
    padding: 0;
  }
}

/* Tablet portrait: the running balance column goes (head and rows), and the
   unpaid charges stack over the rent account with a hairline between. */
@media (min-width: 768px) and (max-width: 999px) {
  /* The footnote explains a column that is not drawn here. */
  .rent-foot-balance {
    display: none;
  }
}

@media (max-width: 999px) {
  .rent-ledger .rent-line {
    grid-template-columns: 96px minmax(0, 1fr) 90px 150px;
  }
  .rent-ledger .rent-line > .rent-line-after {
    display: none;
  }
  .rent-drawer-split {
    grid-template-columns: minmax(0, 1fr);
  }
  .rent-col-charges {
    border-right: 0;
    border-bottom: 1px solid var(--border-color-light);
  }
}

@media (max-width: 767px) {
  .rent-line {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .rent-line-head {
    display: none;
  }

  /* A collapsed row is one tap target (RentView wire()); its Open ledger is
     drawn as a chevron after the balance, its words kept as its name. */
  .rent-row:not(.is-open) {
    padding: 12px 14px;
    cursor: pointer;
  }
  .rent-row:not(.is-open) .rent-open {
    justify-content: center;
    padding: 0 4px;
    border-color: transparent;
    background: transparent;
    color: var(--text-secondary);
  }
  .rent-open-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  .rent-open-chevron {
    display: block;
  }
  .rent-facts {
    font-size: 13px;
  }

  /* Import history… and Note templates… (phone walk, 2026-10-09): on their
     own line as two equal buttons, not pushed to the right under Change…. */
  .rent-summary-actions {
    flex: 1 1 100%;
    margin-left: 0;
  }
  .rent-summary-actions > .btn-secondary {
    flex: 1 1 0;
    justify-content: center;
  }

  /* The open card: 14px in from its edge throughout. */
  .rent-row-head {
    padding: 14px 14px 10px;
  }
  .rent-drawer-note,
  .rent-drawer-bar {
    padding: 0 14px 14px;
  }
  .rent-report {
    padding: 10px 14px;
  }
  .rent-col {
    padding: 14px;
  }
  .rent-ledger {
    padding: 14px 14px 6px;
  }

  /* The action bar as a grid: Record payment… across the top, then Credit…,
     Print and More as equal buttons. The short words are the long ones with
     a tail hidden; each button's aria-label keeps the whole name. */
  /* Compound: .rent-drawer-actions (display: flex) sits below this block. */
  .rent-drawer-actions.rent-drawer-bar {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .rent-drawer-bar > [data-rent-record="payment"] {
    grid-column: 1 / -1;
  }
  .rent-drawer-bar > button {
    justify-content: center;
  }
  .rent-long {
    display: none;
  }
  .rent-drawer-bar .rent-more {
    margin-left: 0;
  }
  .rent-drawer-bar .rent-more .row-menu-trigger {
    width: 100%;
    justify-content: center;
  }
  /* More is a bottom sheet here (the shared .row-menu-list phone rule); the
     drawer's own z-index above would put it under the page's bottom bar. */
  .rent-more .row-menu-list {
    min-width: 0;
    z-index: 100;
  }

  /* A reported payment: the chip and sentence wrap, Confirm and Reject are
     two equal buttons on their own line. */
  .rent-report-actions {
    flex: 1 1 100%;
    margin-left: 0;
  }
  .rent-report-actions > button {
    flex: 1 1 0%;
    justify-content: center;
  }

  /* The ledger filter is a full-width well, the options sharing it. */
  .rent-ledger-head .rent-filter {
    flex: 1 1 100%;
    display: flex;
  }
  .rent-ledger-head .rent-filter .segmented-option {
    flex: 1 1 0%;
  }

  /* The ledger on a phone, stacked: the kind and the amount on the first
     line, then "date · detail · balance" as one facts line, then the row's
     actions as bordered buttons. A flex wrap, not a grid, so the date, the
     detail and the balance run on as one sentence; .rent-line-entry steps
     aside (display: contents) so its kind and detail are placed apart, and
     the row's ::after is the line break after the amount. */
  .rent-ledger .rent-line {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 0;
    padding: 10px 0;
    font-size: 14px;
  }
  .rent-ledger .rent-line-head {
    display: none;
  }
  .rent-ledger .rent-line::after {
    content: "";
    order: 3;
    flex: 0 0 100%;
  }
  .rent-ledger .rent-line-entry {
    display: contents;
  }
  .rent-ledger .rent-line-kind {
    order: 1;
    flex: 1 1 0%;
    min-width: 0;
  }
  .rent-voided .rent-line-kind {
    text-decoration: line-through;
  }
  .rent-ledger .rent-line > [data-amount] {
    order: 2;
    margin-left: 12px;
    font-weight: 600;
  }
  .rent-ledger .rent-line-date,
  .rent-ledger .rent-line-detail,
  .rent-ledger .rent-line > .rent-line-after {
    display: inline;
    font-size: 13px;
    color: var(--text-secondary);
    text-align: left;
  }
  .rent-ledger .rent-line-date {
    order: 4;
  }
  .rent-ledger .rent-line-detail {
    order: 5;
  }
  .rent-ledger .rent-line-detail::before {
    content: "\00a0·\00a0";
  }
  .rent-ledger .rent-line > .rent-line-after {
    order: 6;
  }
  .rent-ledger .rent-line > .rent-line-after::before {
    /* The space after \00a0 ends the escape; without it "ba" reads as hex. */
    content: "\00a0·\00a0 balance\00a0";
  }
  .rent-line-actions {
    order: 7;
    flex: 1 1 100%;
    justify-content: flex-start;
    gap: 8px;
    padding-top: 4px;
  }
  .rent-line-actions:empty {
    display: none;
  }

  /* Unpaid charges keep their three columns, the date narrower. */
  .rent-charge {
    grid-template-columns: 84px minmax(0, 1fr) auto;
  }
  .rent-row-side {
    gap: 8px;
  }
}

/* Phase 7 step 4b — the Record and Reverse dialogs (RentActions.js). The form
   hides the fields a kind does not take with `hidden`, and .form-group sets
   display:flex, which beats the UA [hidden] rule (L-0009): restated here. */
.rent-form .rent-help {
  margin: 4px 0 0;
  font-size: 12px;
}

.rent-form [hidden] {
  display: none !important;
}

.rent-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.rent-form-problems:empty {
  display: none;
}

.rent-form-problems {
  color: var(--text-danger);
  font-size: 13px;
}

.rent-drawer-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Phase 7 step 5b — the import dialog's column and name pickers
   (RentImportDialog.js): a label beside its picker, stacking on a phone. */
.rent-import-map {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

/* Phase 7 step 5c — the note templates dialog (RentTemplatesDialog.js). The
   wording box and the preview hold a legal document of a page or more, so they
   get the notice card's sizing (.admin-notice-textarea) rather than a UA
   default. The preview keeps the line breaks a resident will see. */
.rent-template + .rent-template {
  margin-top: 8px;
}

.rent-template-version {
  grid-template-columns: 96px minmax(0, 1fr) 72px max-content;
}

/* G6-033: View and Retire sit side by side, never one over the other. */
.rent-template-actions {
  display: flex;
  flex-wrap: nowrap;
  justify-content: flex-end;
  gap: 8px;
  white-space: nowrap;
}

.rent-template-body {
  display: block;
  width: 100%;
  min-height: 240px;
  padding: 12px;
  box-sizing: border-box;
  background-color: var(--bg-input);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  font-family: inherit;
  font-size: 13px;
  line-height: 1.55;
  resize: vertical;
}

.rent-template-body:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.rent-template-text {
  white-space: pre-wrap;
  max-height: 320px;
  overflow-y: auto;
  padding: 12px;
  border: 1px solid var(--border-color-light);
  border-radius: var(--border-radius-md);
  font-size: 13px;
  line-height: 1.55;
}

.rent-template-help ul {
  margin: 6px 0 0;
  padding-left: 16px;
  font-size: 13px;
}

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

/* Phase 7 step 7b — the promissory note dialog (RentNoteDialog.js): one row per
   payment (a date and an amount), the spread helper as one wrapping line, and
   a drawn signature at a readable size in the text colour. */
.rent-note-row {
  grid-template-columns: 88px minmax(0, 1fr) minmax(0, 1fr) auto;
  align-items: center;
}

.rent-note-spread {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  margin-bottom: 8px;
}

.rent-note-spread .form-input {
  width: auto;
}

.rent-note-spread input[type="number"] {
  width: 72px;
}

.rent-note-signed {
  margin-top: 8px;
  color: var(--text-primary);
}

.rent-note-signature {
  display: block;
  width: 100%;
  max-width: 360px;
  height: auto;
  border-bottom: 1px solid var(--border-color);
}

.rent-note-void {
  margin-top: 8px;
  padding: 12px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
}

.rent-note-amount {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 0;
}

.rent-note-amount .form-input {
  flex: 1 1 auto;
  min-width: 0;
}

@media (max-width: 767px) {
  .rent-note-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

/* Remove is a row action, not a second field (G6-049). */
@media (max-width: 767px) {
  .rent-note-remove {
    justify-self: end;
  }
}

/* An Out / Back code slot with nothing in it takes no room in the header chip row. */
.resident-record-chips [data-outing-code-slot]:empty {
  display: none;
}

.supplies-house h6 {
  margin: 8px 0 4px;
  font-size: 12px;
  color: var(--text-secondary);
}

.supplies-lines {
  display: flex;
  flex-direction: column;
  font-size: 13px;
}

/* Phase 11 R4e: every .supplies-row is also a .list-row inside a .line-list,
   which gives the padding and the hairline; this keeps only the columns
   (below .list-row, so its template wins). */
.supplies-row {
  grid-template-columns: minmax(0, 1fr) 48px auto auto auto;
  gap: 8px;
}

.supplies-qty {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.supplies-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 80px;
  gap: 4px 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border-color-light);
  align-items: center;
}

.supplies-line .supplies-hint {
  grid-column: 1 / -1;
  font-size: 12px;
  color: var(--text-secondary);
}

.supplies-line .supplies-hint.supplies-low {
  color: var(--text-warning);
}

.supplies-tick {
  display: flex;
  align-items: center;
  gap: 8px;
}

.supplies-batch {
  padding: 6px 0;
  border-bottom: 1px solid var(--border-color-light);
  font-size: 13px;
}

.supplies-batch ul {
  margin: 4px 0;
  padding-left: 16px;
}

/* The pad. `touch-action: none` keeps a finger dragged across it from
   scrolling the page, which would cancel the pointer stream mid-signature
   (the same rule as .client-sig-pad in client.css). */
.supplies-sig-pad {
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  width: 100%;
  min-height: 200px;
  background: var(--bg-input);
  border: 1px dashed var(--border-color);
  border-radius: var(--border-radius-md);
  overflow: hidden;
}

.supplies-sig-ink {
  display: block;
  width: 100%;
  height: 200px;
  color: var(--text-primary);
}

/* A chip or a button in a row never breaks mid-word (the row's `anywhere`
   is for text). */
.supplies-row .chip,
.supplies-row button {
  overflow-wrap: normal;
}

/* Phone: the name and count on the first line; "since", a chip and Replace…
   or Resolve… flow two to a line under them. The second column is sized by
   its widest cell, so a chip is never clipped; a button keeps its own width. */
@media (max-width: 767px) {
  .supplies-row {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .supplies-row > button {
    justify-self: start;
  }
}

/* Phase 6 step 5 — the return checklist: an item, then how many came back,
   went missing or came back damaged. */
.supplies-return {
  display: grid;
  grid-template-columns: minmax(0, 1fr) repeat(3, 84px);
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border-color-light);
  align-items: end;
  font-size: 13px;
}

.supplies-return-count {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
  color: var(--text-secondary);
}

@media (max-width: 767px) {
  .supplies-return {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .supplies-return > span {
    grid-column: 1 / -1;
  }
}

/* ─────────────────────────────────────────────────────────────────────────────
   REQUIREMENTS (the overhaul's Requirements handoff) —
   src/js/components/RequirementManagerView.js
   The summary line (counts, Add requirement… on the right, pinned at the
   bottom on a phone), then one card: the head, a column head and a table row
   per requirement (number · name · Tracked chip · Default target · Edit… and
   ⋯), and the footnote. Add and Edit open one dialog
   (.modal-container[data-req-dialog]). The card does NOT clip (overflow
   visible), so the last row's menu opens fully drawn past the list.
   ───────────────────────────────────────────────────────────────────────────── */
.req-page {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.req-page > * {
  flex-shrink: 0;
  margin: 0;
}

.req-summary-residents {
  white-space: nowrap;
}

/* No height of its own: .btn-primary's padding around the 16px icon is the
   32px control, and on a phone the bottom bar's 44px rule then applies. */
.req-add {
  font-size: 12px;
}

.req-card {
  --cols: 28px minmax(0, 1.4fr) 150px minmax(0, 1fr) 120px;
  overflow: visible;
}

.req-head-note {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-secondary);
}

.req-row {
  gap: 12px;
  padding: 8px 14px;
}

.req-row:last-child {
  border-bottom: none;
}

/* Inactive rows go quiet by colour, not opacity (an opacity on the row would
   also fade the ⋯ menu that opens inside .req-actions). */
.req-row.is-inactive .req-name {
  /* A fade to 0.65 left the chips and number near 2.7:1. The row now reads as
     inactive by its chip and a quieter name, all at 4.5:1 or better. */
  color: var(--text-secondary);
}

.req-num {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

.req-main {
  min-width: 0;
}

.req-title {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
}

.req-name {
  font-size: 14px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.req-target {
  font-size: 13px;
  color: var(--text-secondary);
}

.req-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
}

/* Edit… and ⋯ are the style guide's 28px bordered row buttons with a mouse;
   the 44px floor for a finger is in the (pointer: coarse) block. */
.req-row-btn {
  min-height: var(--control-row-h);
  padding: 0 10px;
  font-size: 12px;
}

.req-note {
  /* Keeps the full-width rule; only the words stop at a readable measure. */
  max-width: none;
  padding: 10px max(14px, calc(100% - 75ch)) 10px 14px;
  border-top: 1px solid var(--border-color-light);
}

/* The Add / Edit dialog: 460px, the fields two across with the name over
   both, Cancel then the primary in the footer. */
.modal-container[data-req-dialog] {
  --modal-pad: 20px;
  max-width: 460px;
  padding: 20px 24px;
}

.modal-container[data-req-dialog] .modal-header {
  margin-bottom: 6px;
}

.modal-container[data-req-dialog] .modal-footer {
  margin-top: 16px;
}

.req-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px;
  align-items: start;
}

.req-form .form-group {
  margin: 0;
}

.req-form-note,
.req-form-wide,
.req-form-name {
  grid-column: 1 / -1;
}

.req-form-note {
  margin: 0 0 4px;
  font-size: 12px;
  color: var(--text-secondary);
}

/* A daily checklist (7) and a yes-or-no (1) fix the target: read-only. */
.req-form .form-input[readonly] {
  opacity: 0.6;
}

/* [hidden] restated: each class here sets its own display. */
.req-page[hidden],
.req-title[hidden],
.req-actions[hidden],
.req-form[hidden] {
  display: none !important;
}

/* Phone (style guide §5): a row stacks as number · name | Tracked chip, the
   target under the name, then Edit… (filling the line) and ⋯ on their own
   line. The dialog is the sheet every dialog is, one field per line. */
@media (max-width: 767px) {
  .req-row {
    grid-template-columns: 24px minmax(0, 1fr) auto;
    gap: 6px 10px;
    padding: 12px 14px;
  }

  .req-name {
    font-size: 15px;
  }

  .req-target {
    grid-column: 2 / -1;
  }

  .req-actions {
    grid-column: 1 / -1;
  }

  .req-actions .edit-req-btn {
    flex: 1 1 auto;
    justify-content: center;
  }

  .modal-container[data-req-dialog] {
    --modal-pad: 16px;
    max-width: none;
    /* W14: the top padding equals --modal-pad so the pinned header sits flush. */
    padding: 16px 16px calc(16px + env(safe-area-inset-bottom, 0px));
  }

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

  .req-form label {
    font-size: 13px;
  }
}

/* ─────────────────────────────────────────────────────────────────────────────
   HOUSES & BEDS (v1.39, the Houses & beds design handoff) —
   src/js/components/HousesView.js and HouseSettings.js
   Top to bottom, 14px apart: the mapping banner (or the one-line "Admission runs
   on beds"), the summary row with Add house, Place the residents, the building
   (one card per house, rooms as columns, beds as tiles, Move / Rename /
   Archive in a ⋯ menu) and the per-house settings card (one house picker, one
   tab strip over Supplies and Out / Back). The page is a block-level column
   of cards rather than a flex column: a card with overflow: hidden collapses
   inside a flex column that is short of room.
   ───────────────────────────────────────────────────────────────────────────── */
.houses-page {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.houses-page > * {
  flex-shrink: 0;
  margin: 0;
}

/* 1. The banner while mapping is open; its border turns green at zero. */
.houses-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  padding: 12px 16px;
}

.houses-banner.is-ready {
  border-color: var(--color-success);
}

.houses-banner-text {
  flex: 1 1 320px;
  min-width: 0;
}

.houses-banner-title {
  font-size: 14px;
  font-weight: 650;
}

.houses-banner-line {
  margin-top: 2px;
  font-size: 12px;
  color: var(--text-secondary);
}

.houses-count-left {
  color: var(--text-warning);
}

.houses-count-done {
  color: var(--text-success);
}

.houses-banner-offline {
  margin: 8px 0 0;
}

.houses-finish {
  min-height: 34px;
  padding: 0 14px;
  white-space: nowrap;
}

/* Disabled, the switch reads as unavailable rather than faded brand. */
.houses-finish:disabled,
.houses-finish:disabled:hover {
  background-color: var(--bg-input-focus);
  color: var(--text-muted);
  opacity: 1;
  cursor: not-allowed;
}

/* After the switch: one line, no card. */
.houses-done {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  font-size: 12px;
  color: var(--text-secondary);
}

/* 2. The summary row. */
.houses-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  font-size: 13px;
  color: var(--text-secondary);
}

.houses-stat {
  font-size: 15px;
  color: var(--text-primary);
}

.houses-stat.is-open {
  color: var(--text-success);
}

.houses-add-house {
  margin-left: auto;
  min-height: 32px;
  padding: 0 12px;
  font-size: 12px;
}

/* 3. Place the residents: a list card outlined in warning. */
.houses-mapping {
  border-color: var(--color-warning);
}

.houses-mapping .list-card-head {
  flex-wrap: wrap;
  gap: 4px 10px;
}

.houses-mapping-title {
  font-size: 13px;
  font-weight: 650;
}

.houses-mapping-sub {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-secondary);
}

.houses-mapping-note {
  margin: 0;
  padding: 8px 16px;
  font-size: 12px;
  border-bottom: 1px solid var(--border-color-light);
}

.med-count-offline.houses-mapping-note {
  border-radius: 0;
}

.houses-map-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(160px, 1.4fr) auto;
  gap: 12px;
  align-items: center;
  min-height: 46px;
  padding: 8px 16px;
  border-bottom: 1px solid var(--border-color-light);
}

.houses-map-row:last-child {
  border-bottom: none;
}

.houses-map-name {
  min-width: 0;
  font-size: 14px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.houses-map-old {
  min-width: 0;
  font-size: 12px;
  color: var(--text-secondary);
}

.houses-map-old [data-legacy-placement] {
  color: var(--text-primary);
}

.houses-map-select {
  min-width: 0;
  width: 100%;
}

/* 4. The building: one full-width card per house. Side by side, a tall house
   beside a short one left a blank block under the short one (House A 8 beds,
   House B 4), as the Settings and resident Profile cards did before they were
   stacked. */
.houses-structure {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}

.houses-structure > .empty-state-container {
  grid-column: 1 / -1;
}

.house-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  padding: 10px 12px 10px 16px;
  border-bottom: 1px solid var(--border-color-light);
}

.house-card-title {
  min-width: 0;
  font-size: 15px;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.house-card-meta {
  font-size: 12px;
  color: var(--text-secondary);
}

.house-card-actions {
  display: flex;
  gap: 4px;
  margin-left: auto;
}

/* "Who lives here" (Men / Women / Any): a small labelled select in the house's header. */
.house-population {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-secondary);
}

.house-population .house-population-select {
  width: auto;
  padding: 2px 24px 2px 8px;
  font-size: 12px;
}

/* The mouse size is 28px; a finger gets the 44px floor from the (pointer: coarse)
   block above (.houses-page .house-population-select). Kept in a (pointer: fine)
   block so it cannot out-rank that floor. */
@media (pointer: fine) {
  .house-population .house-population-select {
    min-height: 28px;
  }
}

/* The ⋯ button beside Add room: square, the same 28px. */
.btn-icon-only {
  justify-content: center;
  min-width: 28px;
  padding: 0;
}

/* Row 27: the house's address, one muted line under its head. */
.house-card-address {
  margin: 0;
  padding: 0 16px 8px;
  font-size: 13px;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}
.house-card-empty {
  margin: 0;
  padding: 20px 16px;
  font-size: 13px;
  color: var(--text-muted);
}

/* Rooms as columns. Each cell draws its right and bottom hairline; the card's
   overflow: hidden trims the ones on the outer edge. */
.house-rooms {
  display: grid;
  /* auto-fit: a house with two rooms shares its card between them instead of
     leaving empty tracks to their right. */
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  margin: 0 -1px -1px 0;
}

.house-room {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  min-height: 110px;
  padding: 10px 12px 8px;
  border-right: 1px solid var(--border-color-light);
  border-bottom: 1px solid var(--border-color-light);
}

.house-room-head {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.house-room-label {
  min-width: 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.house-room-count {
  font-size: 11px;
  color: var(--text-muted);
}

.house-room-menu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  margin-left: auto;
  border-radius: var(--border-radius-sm);
  color: var(--text-muted);
}

.house-room-menu:hover,
.house-room-menu[aria-expanded="true"] {
  background-color: var(--bg-card-hover);
  color: var(--text-primary);
}

/* A bed: occupied on the input fill, open outlined in green. The whole tile
   opens the bed's menu. */
.bed-tile {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 34px;
  padding: 6px 8px;
  border: 1px solid var(--border-color-light);
  border-radius: var(--border-radius-md);
  text-align: left;
  transition: background-color var(--transition-fast);
}

.bed-tile.is-occupied {
  background-color: var(--bg-input);
}

.bed-tile.is-open {
  background-color: transparent;
  border-color: var(--color-success);
}

.bed-tile:hover,
.bed-tile[aria-expanded="true"] {
  background-color: var(--bg-card-hover);
}

.bed-tile-label {
  flex-shrink: 0;
  max-width: 40%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-secondary);
}

.bed-tile-occupant {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 600;
}

.bed-tile-open {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-success);
}

.house-add-bed {
  align-self: flex-start;
  margin-top: auto;
  min-height: 24px;
  padding: 2px 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--color-brand);
}

.house-add-bed:hover {
  color: var(--color-brand-hover);
}

/* The node menu: a 208px popover, fixed to the window under its trigger. */
.node-menu {
  position: fixed;
  z-index: 900;
  display: flex;
  flex-direction: column;
  width: 208px;
  max-height: calc(100vh - 16px);
  overflow-y: auto;
  padding: 6px;
  background-color: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-lg);
  box-shadow: var(--shadow-lg);
}

.node-menu-title {
  padding: 6px 10px;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.node-menu-item {
  padding: 8px 10px;
  border-radius: var(--border-radius-md);
  font-size: 13px;
  text-align: left;
}

.node-menu-item:hover:not(:disabled),
.node-menu-item:focus-visible {
  background-color: var(--bg-card-hover);
}

.node-menu-item.is-danger {
  color: var(--text-danger);
}

.node-menu-item:disabled {
  color: var(--text-muted);
  cursor: not-allowed;
}

.node-menu-note {
  margin: 0;
  padding: 2px 10px 6px;
  font-size: 11px;
  color: var(--text-muted);
}

/* 5. The per-house settings card: picker and tabs on one header line. */
.house-settings-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  padding: 10px 16px 0;
  border-bottom: 1px solid var(--border-color-light);
}

.house-settings-head .segmented {
  margin-bottom: 10px;
  max-width: 100%;
  overflow-x: auto;
}

.house-settings-head .segmented-option {
  min-height: 32px;
  font-weight: 600;
  white-space: nowrap;
}

/* The tabs sit on the header's own hairline, not one of their own. */
.house-settings-tabs {
  gap: 2px;
  border-bottom: none;
  max-width: 100%;
}

.house-settings-tabs .tab {
  padding: 8px 10px;
}

/* A warning count on a tab (Low items, unresolved holdings). */
.tab-count-warning {
  color: var(--text-warning);
  background-color: rgba(var(--marginal-rgb), 0.12);
}

.settings-empty {
  margin: 0;
  padding: 16px;
  font-size: 13px;
}

/* A notice above a panel (offline, could not read again). */
.settings-note {
  margin: 0;
  padding: 8px 16px;
  border-radius: 0;
  font-size: 12px;
}

.supplies-stale.settings-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
}

.supplies-stale.settings-note .med-count-offline {
  margin: 0;
}

/* Table rows inside the card, and their header band. */
.settings-row,
.settings-band {
  display: grid;
  gap: 10px;
  align-items: center;
  padding: 6px 16px;
  border-bottom: 1px solid var(--border-color-light);
  font-size: 13px;
}

.settings-row {
  min-height: 40px;
}

.settings-band {
  background-color: var(--bg-sidebar);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.settings-row-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
}

.settings-meta {
  font-size: 12px;
  color: var(--text-secondary);
}

.settings-hint {
  margin: 8px 0 0;
  font-size: 11px;
  color: var(--text-muted);
}

.settings-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
}

.settings-archived {
  font-size: 12px;
  color: var(--text-secondary);
}

.settings-archived summary {
  cursor: pointer;
}

.settings-archived-list {
  margin-top: 6px;
  border-top: 1px solid var(--border-color-light);
}

.settings-archived-list .settings-row {
  padding-left: 0;
  padding-right: 0;
}

.supplies-stock-row {
  grid-template-columns: minmax(0, 1fr) 90px 120px auto;
}

.supplies-item-row {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
}

.supplies-item-label {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  min-width: 0;
}

.supplies-item-row .supplies-item-label {
  font-weight: 600;
}

.supplies-qty {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.supplies-qty.is-low {
  color: var(--text-warning);
}

.form-input.supplies-reorder-input {
  width: 76px;
  padding: 5px 6px;
  text-align: center;
}

/* The count history dialog's lines (SuppliesAdmin.js openCountHistory). */
.supplies-count-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border-color-light);
  font-size: 13px;
}

/* Columns of short lists: the Replace list, Out / Back's two tabs. */
.settings-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 16px;
  padding: 12px 16px;
}

.settings-col {
  min-width: 0;
}

.settings-col-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}

.settings-col-head .settings-col-title {
  margin: 0;
}

.settings-col-title {
  margin: 0 0 6px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.settings-col-note {
  margin: 0 0 6px;
  font-size: 12px;
  color: var(--text-muted);
}

.settings-col-note strong {
  color: var(--text-secondary);
}

.settings-col-none {
  margin: 0;
  padding: 5px 0;
  border-top: 1px solid var(--border-color-light);
  font-size: 13px;
}

/* The state block inside a settings column: the same block, less air. */
.settings-col-none.empty-state-container {
  padding: 14px 8px;
}

.settings-col-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 4px 8px;
  min-height: 34px;
  padding: 5px 0;
  border-top: 1px solid var(--border-color-light);
  font-size: 13px;
}

.settings-col-row > :first-child {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
}

.settings-col-row + .settings-col-note {
  margin-top: 10px;
}

/* Out / Back: the week of curfews on the left, the facts on the right. */
.outings-curfew-cols {
  grid-template-columns: minmax(0, 1.6fr) minmax(260px, 1fr);
}

/* Seven tiles a line where they fit; on a narrower column they wrap onto a
   second line rather than squeeze a clock time out of its tile (review round
   4): 80px holds "12:30 AM" at 14px/650 inside 10px of padding each side. */
.outings-days {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
  gap: 6px;
}

/* A desktop card holds six 80px tiles and strands Sunday on a row of its own
   (G5-039). Four across gives 4 + 3; where the card is wide enough for all
   seven, the week is one row. Tablets (84-95px tiles) already fit seven. */
@media (min-width: 1200px) {
  .outings-days {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (min-width: 1700px) {
  .outings-days {
    grid-template-columns: repeat(7, minmax(0, 1fr));
  }
}

.outings-day {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  min-width: 0;
  min-height: 72px;
  padding: 8px 10px;
  background-color: var(--bg-input);
  border: 1px solid var(--border-color-light);
  border-radius: var(--border-radius-md);
  text-align: left;
  transition: border-color var(--transition-fast);
}

.outings-day:hover {
  border-color: var(--color-brand);
}

.outings-day-name {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.outings-day-time {
  font-size: 14px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* A night with no curfew reads quieter than a time, and may wrap. */
.outings-day:not([data-curfew-time^="0"]):not([data-curfew-time^="1"]):not([data-curfew-time^="2"]) .outings-day-time {
  font-size: 13px;
  font-weight: 500;
  white-space: normal;
  color: var(--text-secondary);
}

.outings-day-note {
  margin-top: -4px;
  font-size: 11px;
  color: var(--text-muted);
}

.outings-day-next {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-warning);
}

.outings-facts {
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-size: 13px;
}

.outings-fact {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border-color-light);
}

.outings-fact .settings-meta {
  font-size: 13px;
}

.outings-fact-last {
  padding-bottom: 0;
  border-bottom: none;
}

.outings-fact-end {
  margin-left: auto;
}

.outings-overrides {
  display: flex;
  flex-direction: column;
}

.outings-override {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 4px 8px;
  padding: 5px 0;
  border-top: 1px solid var(--border-color-light);
  font-size: 12px;
}

.outings-override > :first-child {
  flex: 1 1 200px;
  min-width: 0;
  overflow-wrap: anywhere;
}

.outings-override[data-outings-override-ended] {
  color: var(--text-secondary);
}

.outings-two {
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
}

.outings-destination-row > :first-child {
  flex: 1 1 120px;
}

.outings-tablet-label {
  font-weight: 600;
}

.outings-tablet-row.is-revoked {
  color: var(--text-muted);
}

.outings-tablet-row.is-revoked .outings-tablet-label,
.outings-tablet-row.is-revoked > :first-child {
  font-weight: 400;
}

/* "Reorder at" beside the box is a phone's word only; above it the column
   head says it. */
.supplies-reorder-word {
  display: none;
}

/* Tablet portrait (< 1000px, the style guide's column drop): one house per
   line with 160px rooms; a Place row is a 2x2 grid (name, old record / the
   picker, Place on the right); the settings tabs get their own line under
   the house picker; the two Out / Back tabs stack; day tiles from 88px. */
@media (max-width: 999px) {
  .houses-structure {
    grid-template-columns: minmax(0, 1fr);
  }
  .house-rooms {
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  }
  .houses-map-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 8px 12px;
    padding: 10px 16px;
  }
  .houses-map-row > [data-map-save] {
    justify-self: end;
    justify-content: center;
    min-width: 110px;
  }
  .house-settings-head .segmented {
    flex: 0 0 auto;
  }
  .house-settings-tabs {
    flex: 1 1 100%;
  }
  .outings-curfew-cols,
  .outings-two {
    grid-template-columns: minmax(0, 1fr);
  }
  .outings-days {
    grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  }
}

/* Phone (< 768px). Add house is in the pinned bottom bar (.view-bottombar);
   its 44px is restated because .houses-add-house sits below the bar's rule.
   The banner's switch and Restock… fill the line. A Place row is one column.
   Rooms from 150px (two across). The house picker fills its line and the
   tab strip scrolls sideways under a right-edge fade. A Stock row drops the
   column head: name and Low | on hand, then "Reorder at [box]" | Count….
   An Items row: name | Edit… Archive, the meta line under. A destination's
   chip drops under its label. Curfew tiles four across (4 + 3, no lone Sunday). */
@media (max-width: 767px) {
  .view-bottombar > .houses-add-house {
    min-height: 44px;
    margin-left: 0;
    font-size: 14px;
  }
  .houses-finish,
  .settings-foot > .supplies-restock {
    flex: 1 1 100%;
    justify-content: center;
  }
  .houses-map-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
    padding: 12px 14px;
  }
  .houses-map-row > [data-map-save] {
    justify-self: stretch;
    justify-content: center;
    min-width: 0;
  }
  .house-rooms {
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  }
  .house-settings-head .segmented {
    flex: 1 1 100%;
  }
  /* R5/W15: grow to fill, never shrink below the label; the strip scrolls. */
  .house-settings-head .segmented-option {
    flex: 1 0 auto;
  }
  /* The fade says there is more to the right. It is exactly as wide as the
     24px of padding after the last tab, so at the end of the strip the last
     tab has scrolled fully clear of it. */
  .house-settings-tabs {
    padding-right: 24px;
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(90deg, #000 calc(100% - 24px), transparent);
  }
  .settings-band.supplies-stock-row {
    display: none;
  }
  .supplies-stock-row {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 6px 10px;
    padding: 10px 14px;
  }
  .supplies-reorder-cell {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    color: var(--text-secondary);
  }
  .supplies-reorder-word {
    display: inline;
  }
  /* The column head is hidden here, so the figure keeps its own word (G5-050). */
  .settings-row.supplies-stock-row .supplies-qty::before {
    content: 'On hand ';
    font-size: 12px;
    font-weight: 400;
    color: var(--text-secondary);
  }
  .form-input.supplies-reorder-input {
    width: 56px;
  }
  .supplies-item-row {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 4px 10px;
  }
  .supplies-item-row > .settings-meta {
    grid-column: 1 / -1;
    grid-row: 2;
  }
  .outings-days {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
  .outings-destination-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .outings-destination-row > :nth-child(2) {
    grid-column: 1;
    grid-row: 2;
  }
  .outings-destination-row > :nth-child(2):empty {
    display: none;
  }
  .outings-destination-row > .settings-row-actions {
    grid-column: 2;
    grid-row: 1 / span 2;
  }

  /* The node menu is a bottom sheet, as the row menu is (rowMenu.js): the
     page dims behind it (the wide shadow) and the transparent ::before over
     the whole screen takes the tap that closes it, so that tap never reaches
     a bed underneath. 48px items; the compound selector outranks the 44px
     coarse-pointer floor. */
  .node-menu {
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    max-height: 80dvh;
    padding: 8px 8px calc(8px + env(safe-area-inset-bottom, 0px));
    border-bottom: none;
    border-radius: var(--border-radius-sheet) var(--border-radius-sheet) 0 0;
    box-shadow: 0 0 0 100vmax var(--bg-overlay);
  }
  .node-menu::before {
    content: '';
    position: fixed;
    inset: 0;
    z-index: -1;
  }
  .node-menu .node-menu-item {
    min-height: 48px;
    font-size: 14px;
  }
}

/* ── Out / Back (phase 8) ─────────────────────────────────────────────────────
   The board (OutBackView.js) and the admin card (OutingsAdmin.js). A flag is
   a word, never a colour alone: "Past curfew" reads the same in either theme
   and to a screen reader.

   Phase 11 O1: the board on the handoff. Flags are F1 .chip tones (the old
   .outing-flag is retired); houses are F1 list cards in a grid; each row is
   an F1 .list-row. "As of" and Read again live in the header slot. */

/* The summary row: "N out now", the past-curfew and flagged chips, then the
   three actions pushed right (they wrap under the counts on a phone). */
.out-back-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
}

.out-back-summary[hidden] {
  display: none;
}

.out-back-count {
  font-size: 15px;
  font-weight: 650;
}

/* "Curfew tonight: 11:00 PM" (v1.40), when every house has the same one. */
.out-back-curfew {
  font-size: 13px;
  color: var(--text-secondary);
}

.out-back-curfew strong {
  color: var(--text-primary);
}

/* A house card's curfew tonight, at the end of its head. */
.out-back-house-curfew {
  margin-left: auto;
  font-size: 12px;
  font-weight: 400;
  color: var(--text-secondary);
}

.out-back-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.out-back-actions[hidden] {
  display: none;
}

/* The summary's buttons: 32px with a mouse. `height`, never `min-height` (the
   My runs technique): this block is BELOW the (pointer: coarse) block, so a
   min-height here would replace that block's 44px floor for .btn-primary /
   .btn-secondary at equal specificity. A height is clamped up by the floor's
   min-height instead, so a finger still gets 44px. The labels never wrap. */
.out-back-btn {
  height: 32px;
  padding: 0 12px;
  white-space: nowrap;
}

.out-back-note {
  max-width: 75ch;
  margin: 6px 0 14px;
  font-size: 12px;
  color: var(--text-muted);
}

/* "As of 7:30 PM", beside Read again in the header slot. */
.out-back-at {
  font-size: 12px;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* The houses: min() keeps one column inside a 390px phone, where a bare 380px
   track would overflow its ~358px of content. */
.out-back-houses {
  /* Columns, not a grid: each house keeps its own height and the next one
     slides up under it, so a short house never leaves a blank block beside a
     tall one (the grid this replaced stopped at 560px and left the right 40%
     of the page empty). The width is a floor of 380px; the columns share
     whatever the page has, so a row's Back home and History stay near the name
     at 1920px too. On a 390px phone the floor is wider than the page and the
     houses are one column. */
  column-width: 380px;
  column-gap: 14px;
  /* column-count is the most columns it may use; the count of houses sets it,
     so two houses are two columns and a lone house is the full width instead of
     the left column of an otherwise empty row. */
  column-count: 1;
}

.out-back-houses:has(> .outing-house:nth-child(2)) {
  column-count: 2;
}

.out-back-houses:has(> .outing-house:nth-child(3)) {
  column-count: 3;
}

.out-back-houses:has(> .outing-house:nth-child(4)) {
  column-count: 4;
}

.out-back-houses > .outing-house {
  break-inside: avoid;
  margin-bottom: 14px;
}

.out-back-houses[hidden] {
  display: none;
}

.out-back-house-name {
  margin: 0;
  font-size: var(--fs-h3);
  font-weight: 650;
}

.out-back-house-count {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-secondary);
}

/* A resident's row: line 1 the name and flags beside the two actions, line 2
   the facts across the whole row. */
.outing-row {
  padding: 10px 16px;
}

.outing-row-main {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 6px;
  min-width: 0;
}

.outing-name {
  font-size: 14px;
  font-weight: 600;
}

.outing-facts {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 12px;
  color: var(--text-secondary);
}

.outing-row-past-curfew {
  background-color: rgba(var(--non-compliant-rgb), 0.04);
}

.outing-row-past-curfew .outing-since {
  color: var(--text-danger);
  font-weight: 600;
}

.out-back-empty h4 {
  font-size: var(--fs-h4);
  font-weight: 650;
}

.out-back-empty p {
  font-size: 13px;
}

/* Phase 11 O2: a resident's history (OutBackView.js openHistory). A wide
   dialog with no padding of its own, so the title row, the header band, the
   rows and the footer each carry the 20px gutter. The rows are F1
   .list-row-history (150px / 1fr / 110px / actions; they stack below 600px,
   where the header band goes). Nothing here sizes a button past the coarse
   floor: Excuse a night… is a `height` (clamped up by the floor's
   min-height), and Correct…/Withdraw… are .btn-row. */
.outing-history-dialog {
  max-width: 760px;
  padding: 0;
}

.outing-history-head {
  flex-wrap: wrap;
  justify-content: flex-start;
  align-items: center;
  gap: 6px 10px;
  margin-bottom: 0;
  padding: 14px 20px;
  border-bottom: 1px solid var(--border-color-light);
}

.outing-history-head h2 {
  min-width: 0;
}

.outing-history-span {
  font-size: 12px;
  color: var(--text-secondary);
}

.outing-history-excuse {
  height: 30px;
  margin-left: auto;
  padding: 0 12px;
  white-space: nowrap;
}

/* The band sticks under the pinned title row (the card itself scrolls), so the
   columns keep their names once the list scrolls (walk 2026-10-23). The
   title row's height is measured into the variable by OutBackView.js. */
.outing-history-band {
  position: sticky;
  top: var(--outing-history-head-h, 0px);
  z-index: 1;
  min-height: 0;
  padding: 6px 20px;
  background-color: var(--bg-sidebar);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.outing-history-row {
  padding: 8px 20px;
}

/* Each row is its own grid, so an `auto` action column would be as wide as
   that row's button (Withdraw… is wider than Correct…) and the Source column
   would shift from row to row. A fixed actions cell lines every row, and the
   band, up. */
.outing-history-row > .list-row-actions,
.outing-history-band > :last-child {
  width: 84px;
}

.outing-history-when {
  font-variant-numeric: tabular-nums;
}

.outing-history-entry {
  min-width: 0;
}

.outing-history-kind {
  font-weight: 600;
}

.outing-history-dest,
.outing-history-state,
.outing-history-source {
  color: var(--text-secondary);
}

.outing-history-line .chip {
  vertical-align: middle;
}

/* The first chip after the words: the joining space alone (4px) read as no
   gap, the same as chip to chip. */
.outing-history-line > :not(.chip) + .chip {
  margin-left: 4px;
}

.outing-history-note {
  margin: 2px 0 0;
  font-size: 11px;
  color: var(--text-muted);
}

/* A struck-out entry stays listed: struck through in --text-secondary, and no
   opacity (an opacity would fade it below 4.5:1). Its state word and its note
   are not struck, so they stay readable. */
.outing-history-row[data-voided] {
  color: var(--text-secondary);
}

.outing-history-row[data-voided] .outing-history-when,
.outing-history-row[data-voided] .outing-history-kind,
.outing-history-row[data-voided] .outing-history-dest,
.outing-history-row[data-voided] .outing-history-source {
  color: var(--text-secondary);
  text-decoration: line-through;
}

.outing-history-empty {
  margin: 0;
  padding: 20px;
}

.outing-history-footnote {
  margin: 0;
  padding: 10px 20px;
  border-top: 1px solid var(--border-color-light);
  font-size: 11px;
  color: var(--text-muted);
}

.outing-history-dialog .modal-footer {
  margin-top: 0;
  padding: 12px 20px;
}

/* G3-083: Withdraw… and Correct… share one width so their edges line up. */
.outing-history-dialog .list-row-actions > button {
  min-width: 104px;
  justify-content: center;
}

/* Phase 11 O3: the Out / Back dialogs (Add entry, Back home, Correct, Excuse,
   the resident picker, the Curfew report) lay their fields on F1's
   .form-grid-2, which is one column below 600px. A field with no partner
   spans both columns. Labels 12px/600 secondary; inputs 9px 10px, 14px.
   Padding only on the inputs: the 44px coarse floor (min-height) still
   applies. Where's [hidden] is restated below; the Where pair is itself a .form-grid-2, which restates it. */
.outing-grid .outing-span {
  grid-column: 1 / -1;
}

/* .form-group sets its own display, which beats the UA [hidden] rule: Where is
   hidden for a "Came back" entry (G3-060). */
.outing-grid [data-when-out][hidden] {
  display: none;
}

.outing-grid .form-group {
  margin-bottom: 0;
}

.outing-grid > p {
  margin: 0;
}

.outing-grid .form-group label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
}

.outing-grid .form-input {
  padding: 9px 10px;
  font-size: 14px;
}

/* Overhaul (design_handoff_transportation, Out Back v2): tablet portrait and
   phone. Below every rule above, which these override on source order.

   Below 1000px one house card per row, and the History dialog drops its
   column band and stacks each entry: line 1 the entry (kind, destination,
   flags), line 2 "Source · When" in 12px secondary, Correct… on the right
   across both lines. The markup order stays When, Entry, Source, action;
   only the grid placement changes. The selectors carry the dialog class so
   they outrank F1's phone fallback for .list-row-history (0,3,0, above). */
@media (max-width: 999px) {
  /* The :has() rules above out-rank a bare class, so each is restated. */
  .out-back-houses,
  .out-back-houses:has(> .outing-house:nth-child(2)),
  .out-back-houses:has(> .outing-house:nth-child(3)),
  .out-back-houses:has(> .outing-house:nth-child(4)) {
    column-count: 1;
  }

  .outing-history-band {
    display: none;
  }

  .outing-history-dialog .outing-history-row {
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 4px 0;
  }

  .outing-history-dialog .outing-history-row > .outing-history-entry {
    grid-column: 1 / 3;
    grid-row: 1;
  }

  .outing-history-dialog .outing-history-row > .outing-history-source {
    grid-column: 1;
    grid-row: 2;
    font-size: 12px;
  }

  .outing-history-dialog .outing-history-row > .outing-history-source::after {
    content: "\a0\b7\a0";
  }

  .outing-history-dialog .outing-history-row > .outing-history-when {
    grid-column: 2;
    grid-row: 2;
    font-size: 12px;
    color: var(--text-secondary);
  }

  .outing-history-dialog .outing-history-row > .list-row-actions {
    grid-column: 3;
    grid-row: 1 / span 2;
    width: auto;
    margin-left: 10px;
  }
}

/* Phone: the summary's two utility buttons share a line of their own, equal
   width; Add entry… is in the pinned bottom bar (.view-bottombar, Wave 0).
   A row's Back home… and History take a third line under the facts as two
   equal buttons; the facts go to 13px. Button HEIGHTS are not set here: a
   mouse keeps 32/28px, a finger gets the coarse block's 44px floor. History
   is a tall sheet (92% of the screen), its title row a grid: the title with
   "last 90 days" under it, Excuse a night… and the × on the right. */
@media (max-width: 767px) {
  .out-back-actions {
    flex: 1 1 100%;
    flex-wrap: nowrap;
    margin-left: 0;
  }

  .out-back-actions > .out-back-btn {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
  }

  .outing-row > .outing-row-actions {
    grid-column: 1 / -1;
    grid-row: 3;
    flex-wrap: nowrap;
    justify-content: stretch;
  }

  .outing-row-actions > button {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
  }

  .outing-row > .outing-facts {
    grid-row: 2;
    font-size: 13px;
  }

  /* padding-top: the dialog has no padding of its own (--modal-pad 0), so
     the sheet's grab handle needs room above the title row. */
  .modal-container.outing-history-dialog {
    /* G3-083: a ceiling, not a height; a short history fits its content. */
    max-height: 92vh;
    max-height: 92dvh;
    padding-top: 14px;
  }

  .outing-history-dialog .outing-history-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 2px 8px;
    padding: 0 14px 12px;
  }

  .outing-history-head > h2 {
    grid-column: 1;
    grid-row: 1;
  }

  .outing-history-head > .outing-history-span {
    grid-column: 1;
    grid-row: 2;
  }

  .outing-history-head > .outing-history-excuse {
    grid-column: 2;
    grid-row: 1 / span 2;
    margin-left: 0;
  }

  .outing-history-head > .modal-close {
    grid-column: 3;
    grid-row: 1 / span 2;
  }

  .outing-history-dialog .outing-history-row,
  .outing-history-footnote,
  .outing-history-dialog .modal-footer {
    padding-left: 14px;
    padding-right: 14px;
  }
}

/* Phase 9 step 4 — Transportation (TransportView.js, transportForms.js). */
/* Phase 11 T3: the Today tab on the handoff (README "Screen 2 / Today").
   Two columns, left the day line, the alert and the runs, right the ride
   requests and the vehicle warnings; one column below 900px (the width rule
   after these). The tab row is .tabs-underline (F1); its count is .tab-count.
   Nothing here sizes a button: Verify…/Enter…/Read again are .btn-sm and
   Void…/Approve…/Decline… are .btn-row; the 44px coarse floor for both is in the
   coarse-pointer block above, and must not be replaced from down here.
   Every class that sets display restates [hidden]. */
.transport-tabs {
  margin-bottom: 16px;
}

/* The Compliance screen's two tabs, This week and Trend (Phase 11 X5): the
   same underline strip and the same gap to the panel below as Transportation.
   Two short labels fit at 390px, so the strip never needs its sideways scroll. */
.compliance-tabs {
  margin-bottom: 16px;
}

.transport-today {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(300px, 1fr);
  gap: 16px;
  align-items: start;
}

.transport-today[hidden] {
  display: none;
}

.transport-today-main,
.transport-today-side {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.transport-today-main[hidden],
.transport-today-side[hidden] {
  display: none;
}

.transport-dayline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.transport-dayline[hidden] {
  display: none;
}

.transport-day {
  font-size: 13px;
  color: var(--text-secondary);
}

/* The no-show alert as a danger card (same sentence as before). */
.transport-alert {
  padding: 10px 14px;
  border: 1px solid var(--color-danger);
  border-radius: var(--border-radius-lg);
  background-color: rgba(var(--non-compliant-rgb), 0.06);
  color: var(--text-danger);
  font-size: 13px;
}

.transport-alert strong {
  font-weight: 650;
}

/* A run card: 12px 16px, sections by hairline, never a nested card. */
.transport-run {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 16px;
}

.transport-run[hidden] {
  display: none;
}

.transport-run-line {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
}

.transport-run-line[hidden] {
  display: none;
}

.transport-run-title {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.transport-run-title[hidden] {
  display: none;
}

.transport-run-name {
  font-size: 14px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

/* Overhaul: a run's title stays on one line (an ellipsis if it must), and
   its chips wrap after it instead. max-width keeps a long label inside the
   card at 390px. */
.transport-run-title > .transport-run-name {
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Overhaul: Verify… is outlined in the brand colour, not filled, so a run
   card keeps a single weight (the tab's one primary is New run… and the
   like). A border and a text colour only: its size is .btn-sm's, so the
   coarse block's 44px floor still reaches it. */
.btn-secondary.transport-verify-btn {
  border-color: var(--color-brand);
  color: var(--color-brand);
}

.btn-secondary.transport-verify-btn:hover:not(:disabled) {
  border-color: var(--color-brand);
  background-color: var(--color-brand-glow);
  color: var(--color-brand);
}

.transport-run-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
  margin-left: auto;
}

.transport-run-actions[hidden] {
  display: none;
}

.transport-facts {
  margin: 0;
  font-size: 12px;
  color: var(--text-secondary);
}

.transport-no-riders {
  margin: 0;
  font-size: 13px;
}

.transport-riders-list {
  display: flex;
  flex-direction: column;
  font-size: 13px;
}

.transport-riders-list[hidden] {
  display: none;
}

.transport-rider-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 8px;
  align-items: center;
  padding: 5px 0;
  border-top: 1px solid var(--border-color-light);
}

.transport-rider-line[hidden] {
  display: none;
}

.transport-rider-chips {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 4px;
}

.transport-rider-chips[hidden] {
  display: none;
}

/* The right column's cards: .glass-card list-card, a .list-card-head, rows
   16px in with a hairline above each. A card's own title is 13px/650, as the
   handoff's list heads are (v1.40); a heading on the page between cards
   ("Runs on {day}") is .transport-section-title, 15px. */
/* A vehicle row's ⋯ menu (rowMenu.js, position absolute) must draw past the
   last row, so this list card does not clip (the Admin list's rule). */
.list-card[data-transport-vehicles] {
  overflow: visible;
}

.transport-card-title {
  margin: 0;
  font-size: 13px;
  font-weight: 650;
}

.transport-section-title {
  margin: 0;
  font-size: 15px;
  font-weight: 650;
}

.transport-card-chip {
  margin-left: auto;
}

.transport-card-note {
  margin: 0;
  padding: 10px 16px;
  font-size: 13px;
}

.transport-request {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 16px;
  border-top: 1px solid var(--border-color-light);
  font-size: 12px;
}

.transport-request[hidden] {
  display: none;
}

.list-card-head + [data-transport-requests-list] > .transport-request:first-child {
  border-top: none;
}

.transport-request-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}

.transport-request-foot[hidden] {
  display: none;
}

.transport-request-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
}

.transport-request-actions[hidden] {
  display: none;
}

.transport-card-details {
  border-top: 1px solid var(--border-color-light);
}

.transport-card-details > summary {
  padding: 10px 16px;
  font-size: 12px;
  color: var(--text-secondary);
  cursor: pointer;
}

.transport-warning-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  border-top: 1px solid var(--border-color-light);
  font-size: 13px;
}

.transport-warning-line[hidden] {
  display: none;
}

.list-card-head + .transport-warning-line {
  border-top: none;
}

/* The empty board: a card, not the page-sized empty state. */
.transport-empty {
  padding: 28px 16px;
}

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

/* Phase 11 T4: Runs, Vehicles, Drivers and Places on the handoff (README
   "Screen 2 / Runs … Places"). Each tab is a .transport-stack of list cards
   (F1 .list-card / .list-row with the -assignments, -standing, -vehicles,
   -drivers and -places column templates; the phone fallback is F1's). Row
   actions are the 26px row size and the header adds the 28px small size; the
   tab's one primary is the base .btn-primary. Nothing here sizes a button, so
   the coarse-pointer 44px floor above stands. Every class that sets display
   restates [hidden]. */
.transport-stack {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

.transport-stack[hidden] {
  display: none;
}

/* A long word in a cell wraps inside its column instead of widening the row. */
.transport-stack .list-row > * {
  min-width: 0;
  overflow-wrap: anywhere;
}

.transport-date {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
}

.transport-date[hidden] {
  display: none;
}

.transport-date label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
}

.transport-date .form-input {
  width: auto;
  max-width: 100%;
}

/* The day input inside the shared date-control pill (Phase 11 X1a): the pill
   draws the border, radius, height and size, so the input draws none of its
   own and fills it. No height or min-height here: under a touch pointer the
   single-class .form-input floor lifts it (and the pill with it) to 44px. */
.transport-date .week-navigator .form-input {
  border: 0;
  border-radius: 0;
  background-color: transparent;
  padding: 0 14px;
  font-size: inherit;
  font-weight: 600;
  color: inherit;
}

.transport-date-hint {
  font-size: 11px;
  color: var(--text-muted);
}

.transport-list-head {
  flex-wrap: wrap;
}

.transport-list-title {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 8px;
  min-width: 0;
}

.transport-list-title[hidden] {
  display: none;
}

.transport-list-note {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-secondary);
}

.transport-head-action {
  margin-left: auto;
}

/* "Runs on {day}" with New run… on the right. */
.transport-section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.transport-section-head[hidden] {
  display: none;
}

.transport-row-name {
  font-weight: 600;
}

.transport-row-time {
  font-variant-numeric: tabular-nums;
}

.transport-row-label {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
}

.transport-row-label[hidden] {
  display: none;
}

.transport-row-chips {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
}

.transport-row-chips[hidden] {
  display: none;
}

.transport-intro {
  margin: 0;
  font-size: 12px;
  color: var(--text-secondary);
}

/* Add a vehicle… / Print a cost report… / the Records… sentence; Add a place…. */
.transport-actions-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.transport-actions-row[hidden] {
  display: none;
}

.transport-archived > summary,
.transport-run-details > summary {
  font-size: 12px;
  color: var(--text-secondary);
  cursor: pointer;
  /* The mouse row height. The touch rule earlier in the file pads this to 44px, which is taller, so it still wins. */
  min-height: 24px;
}

.transport-archived > .list-card {
  margin-top: 8px;
}

/* The Runs tab's compact run card: tighter than Today's, the riders and
   Verify… under a <details>. */
.transport-run-compact {
  gap: 4px;
  padding: 10px 16px;
}

.transport-run-fixed {
  align-self: center;
  font-size: 11px;
  color: var(--text-muted);
}

.transport-run-details {
  margin-top: 4px;
}

.transport-run-details-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-top: 6px;
}

.transport-run-details-body[hidden] {
  display: none;
}

.transport-run-verify {
  display: flex;
  justify-content: flex-end;
}

.transport-run-verify[hidden] {
  display: none;
}

.transport-stops,
.transport-riders {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.transport-stop {
  display: grid;
  grid-template-columns: 24px minmax(0, 2fr) minmax(0, 1.5fr) auto;
  gap: 6px;
  align-items: center;
}

.transport-stop [data-stop-other] {
  grid-column: 2 / 4;
  grid-row: 2;
}

/* Remove sits on the stop's first line whether or not the "in words" box is
   there (G7-036). */
.transport-stop > [data-stop-remove] {
  grid-column: 4;
  grid-row: 1;
}

.transport-stop-seq {
  font-weight: 700;
  text-align: center;
}

.transport-rider {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: 6px;
  align-items: center;
}

.transport-rider-stop {
  display: block;
  min-width: 0;
}

/* The "Picked up at" / "Dropped off at" words show on a phone, where the
   selects sit under the name; elsewhere the columns are plain and the label
   is read aloud only. */
.transport-rider-stop-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* A rider who is not ticked: their stop choices look off, not just default (G7-028). */
.transport-rider .form-input:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.transport-weekdays {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  border: 0;
  padding: 0;
  margin: 0;
}

/* Overhaul (design_handoff_transportation, Transportation v2): tablet
   portrait and phone. Below the T4 rules above and below F1's phone fallback
   for the list columns (~line 2150), which these override on source order;
   each selector carries the list's own hook or two classes so it is at least
   as specific as that fallback's (0,3,0 for the middle cells).

   Below 1000px (Today already stacks, above): a vehicle's make and plate
   move under its name, and a place's "Residents can ask" sits under its name. */
@media (max-width: 999px) {
  [data-transport-vehicles] .list-row-vehicles,
  .transport-archived .list-row-vehicles {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) auto;
  }

  .transport-card .list-row-vehicles > :nth-child(1) {
    grid-column: 1;
    grid-row: 1;
  }

  .transport-card .list-row-vehicles > :nth-child(2) {
    grid-column: 1;
    grid-row: 2;
  }

  .transport-card .list-row-vehicles > :nth-child(3) {
    grid-column: 2;
    grid-row: 1 / span 2;
  }

  .transport-card .list-row-vehicles > :nth-child(4) {
    grid-column: 3;
    grid-row: 1 / span 2;
  }

  [data-transport-places] .list-row-places {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr) auto;
  }

  /* Residents can ask: under the name and kind, not dropped (G7-031). */
  [data-transport-places] .list-row-places > :nth-child(1) {
    grid-column: 1;
    grid-row: 1;
  }

  [data-transport-places] .list-row-places > :nth-child(2) {
    grid-column: 2;
    grid-row: 1;
  }

  [data-transport-places] .list-row-places > :nth-child(3) {
    grid-column: 1 / 3;
    grid-row: 2;
  }

  [data-transport-places] .list-row-places > :nth-child(3):empty {
    display: none;
  }

  [data-transport-places] .list-row-places > :nth-child(4) {
    grid-column: 3;
    grid-row: 1 / span 2;
  }
}

/* Phone: the five tabs share the width; Assignments rows are a 2×2 grid
   (vehicle · driver over shift · Edit… Remove); a standing run is time and
   label on line one, its days on line two, the buttons on the right; a
   vehicle or a place stacks to one column; a driver is name, licence and
   chip stacked on the left with Set… on the right. Not recorded stacks what
   over why over a full-width Dismiss. Heights are never set here: a mouse
   keeps the desktop sizes, a finger gets the coarse block's floor. */
@media (max-width: 767px) {
  .transport-tabs {
    gap: 0;
  }

  /* A run's Enter… / Edit… / Cancel… (phone walk, 2026-10-09): when they wrap
     under the title they take the line as equal buttons, not a lone button
     at the right edge. */
  .transport-run-actions {
    flex: 1 1 100%;
    justify-content: stretch;
    margin-left: 0;
  }
  .transport-run-actions > button {
    flex: 1 1 0;
    justify-content: center;
  }

  .transport-tabs .tab {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
    padding-left: 4px;
    padding-right: 4px;
  }

  [data-transport-assignments] .list-row-assignments {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }

  [data-transport-assignments] .list-row-assignments > :nth-child(n) {
    grid-column: auto;
    grid-row: auto;
  }

  [data-transport-standing] .list-row-standing {
    grid-template-columns: auto minmax(0, 1fr) auto;
  }

  [data-transport-standing] .list-row-standing > :nth-child(1) {
    grid-column: 1;
    grid-row: 1;
  }

  [data-transport-standing] .list-row-standing > :nth-child(2) {
    grid-column: 2;
    grid-row: 1;
  }

  [data-transport-standing] .list-row-standing > :nth-child(3) {
    grid-column: 1 / 3;
    grid-row: 2;
  }

  [data-transport-standing] .list-row-standing > :nth-child(4) {
    grid-column: 3;
    grid-row: 1 / span 2;
  }

  [data-transport-vehicles] .list-row-vehicles,
  .transport-archived .list-row-vehicles,
  [data-transport-places] .list-row-places {
    grid-template-columns: minmax(0, 1fr);
  }

  .transport-card .list-row-vehicles > :nth-child(n),
  [data-transport-places] .list-row-places > :nth-child(n) {
    grid-column: 1;
    grid-row: auto;
  }

  .transport-card .list-row-vehicles > .list-row-actions,
  [data-transport-places] .list-row-places > .list-row-actions {
    justify-content: flex-start;
  }

  [data-transport-drivers] .list-row-drivers {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  [data-transport-drivers] .list-row-drivers > :nth-child(-n + 3) {
    grid-column: 1;
    grid-row: auto;
  }

  [data-transport-drivers] .list-row-drivers > :nth-child(4) {
    grid-column: 2;
    grid-row: 1 / span 3;
  }

  /* One stop is one unit: its number at the left, then where, what happens,
     the words if it is somewhere else, and Remove, stacked in the second
     column in the same order on every stop (G7-039). */
  .transport-stop {
    grid-template-columns: 24px minmax(0, 1fr);
    align-items: start;
  }
  .transport-stop-seq {
    grid-column: 1;
    grid-row: 1;
    text-align: left;
    padding-top: 8px;
  }
  .transport-stop > :not(.transport-stop-seq),
  .transport-stop [data-stop-other],
  .transport-stop > [data-stop-remove] {
    grid-column: 2;
    grid-row: auto;
  }
  .transport-stop > [data-stop-remove] {
    justify-self: end;
  }

  /* A rider: the name on its own line, then the two stops side by side, each
     with its words. */
  .transport-rider {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
  .transport-rider > .supplies-tick {
    grid-column: 1 / -1;
  }
  .transport-rider-stop-label {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    clip: auto;
    display: block;
    font-size: 11px;
    color: var(--text-secondary);
    margin-bottom: 2px;
  }
}

/* Phase 9 step 5 — My runs (MyRunsView.js), restyled in Phase 11 T1 on the
   transportation handoff (README "Screen 1"), in this sheet's tokens.

   Every rule here sits below .glass-card, .btn-primary/.btn-secondary and
   .rent-form, which it overrides on source order. Every class that sets
   display restates [hidden].

   BUTTON SIZE. .my-runs-btn is 32px with a mouse (34 before the overhaul's
   style guide) and must still be 44px under
   a finger. It sets `height`, never `min-height`: this block is BELOW the
   (pointer: coarse) block, so a min-height here would replace that block's
   44px floor for .btn-primary/.btn-secondary at equal specificity. A height is
   clamped up by the floor's min-height instead, so a finger still gets 44px.
   The labels are short and never wrap (white-space: nowrap). */
.my-runs {
  display: flex;
  flex-direction: column;
  gap: 14px;
  /* No width cap: the cards span the page like Out / Back's (G3-067). */
}

.my-runs[hidden] {
  display: none;
}

/* Head row: the day, the pill as a chip, the offline sentence. */
.my-runs-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
}

.my-runs-head[hidden] {
  display: none;
}

.my-runs-day {
  font-size: 13px;
  color: var(--text-secondary);
}

.my-runs-offline {
  font-size: 12px;
  color: var(--text-secondary);
}

.my-runs-secondary {
  color: var(--text-secondary);
}

/* A card on this screen: the assignment, a run, Not recorded, the empty state. */
.my-runs-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px;
}

.my-runs-card[hidden] {
  display: none;
}

/* Line 1 of a card: the name, its chips, and (on a run) Start… / End… right. */
.my-runs-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
}

.my-runs-line[hidden] {
  display: none;
}

.my-runs-name {
  font-size: 15px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  min-width: 0;
  overflow-wrap: anywhere;
}

.my-runs-shift {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-secondary);
}

.my-runs-out {
  font-size: 12px;
  color: var(--text-secondary);
}

.my-runs-run-action {
  margin-left: auto;
}

/* Line 2 of the assignment card: the vehicle's buttons. */
.my-runs-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.my-runs-actions[hidden] {
  display: none;
}

.my-runs-btn {
  height: 32px;
  padding: 0 14px;
  white-space: nowrap;
}

.my-runs-btn-quiet {
  font-size: 12px;
}

/* A run under way carries the brand border. Done and cancelled runs recede
   through their TEXT (--text-secondary, 6.57:1 on the dark card, 5.82:1 on
   white), never the handoff's 0.7 opacity, which took that same text below
   4.5:1. The chips keep their own tones. */
.my-runs-run[data-run-status="in_progress"] {
  border-color: var(--color-brand);
}

.my-runs-run[data-run-status="done"],
.my-runs-run[data-run-status="cancelled"] {
  color: var(--text-secondary);
}

/* Each stop: a hairline, the stop line, then its riders. */
.my-runs-stop {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: 10px;
  border-top: 1px solid var(--border-color-light);
  font-size: 13px;
}

.my-runs-stop[hidden] {
  display: none;
}

.my-runs-stop-label {
  margin: 0;
}

.my-runs-rider {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 8px;
  min-height: 36px;
}

.my-runs-rider[hidden] {
  display: none;
}

/* The tick buttons are the driver's one-handed targets. A wrapping flex, so
   when the row is short of room they wrap under each other rather than push
   the card sideways. */
.my-runs-ticks {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: flex-end;
}

.my-runs-ticks[hidden] {
  display: none;
}

/* Not recorded: a warning border, then what / why / Dismiss rows. */
.my-runs-refused {
  border: 1px solid var(--color-warning);
}

.my-runs-refused-title {
  margin: 0;
  font-size: var(--fs-h4);
  font-weight: 650;
}

.my-runs-refused-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) auto;
  align-items: center;
  gap: 6px 12px;
  font-size: 13px;
}

.my-runs-refused-row[hidden] {
  display: none;
}

.my-runs-refused-what {
  font-weight: 600;
}

.my-runs-footnote {
  margin: 0;
  font-size: 11px;
  color: var(--text-muted);
}

/* The tap dialogs (tapDialog): a narrower column, the odometer larger. */
.my-runs-form {
  gap: 14px;
  max-width: 440px;
}

/* The card is narrowed to the column, so the fields fill it rather than two
   thirds of a 680px card (walkthrough 2026-10-04): 440 plus the card padding. */
.modal-container[data-my-runs-form] {
  max-width: calc(440px + 2 * var(--modal-pad));
}

.my-runs-form .form-group {
  margin-bottom: 0;
}

.my-runs-odometer {
  font-size: 15px;
}

/* Phone: Not recorded stacks what over why over a full-width Dismiss (the
   overhaul handoff), and a tap dialog's odometer is 18px (the sheet's other
   inputs are 16px, Wave 0). The odometer selector out-ranks the sheet's
   :is() input rule (0,3,1). Below the rules they override. */
@media (max-width: 767px) {
  .my-runs-refused-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .my-runs-refused-row > :last-child > button {
    width: 100%;
    justify-content: center;
  }

  .modal-container[data-my-runs-form] .my-runs-form input.my-runs-odometer {
    font-size: 18px;
  }
}

/* ═════════════════════════════════════════════════════════════════════════════
   KEYBOARD FOCUS (Phase 11 A3)
   ═════════════════════════════════════════════════════════════════════════════
   One ring, from the :root tokens (--focus-ring-width, -offset, -inset) and the
   per-theme --focus-ring-color. :focus-visible only, so a mouse click draws
   nothing and a Tab always does.

   This block is the LAST thing in the file on purpose: every rule in it sits
   below every :focus rule that sets `outline: none`, so source order never
   decides against the ring. Those rules keep their outline: none for the mouse;
   the second rule here restates each of them as :focus-visible, at the same
   specificity, so the keyboard gets the ring back.

   Not ringed: the dialog heading ([data-modal-title][tabindex="-1"]), which is
   focused programmatically and keeps its outline: none above, and any
   tabindex="-1" container the dialog shell parks focus on. */

/* The roster search box rings its label (.roster-search:focus-within); the
   input inside it draws none, or the box gets two rings. Two classes, so it
   beats the plain input:focus-visible rule below. */
.roster-search .roster-search-input:focus-visible {
  outline: none;
}

.roster-search:has(.roster-search-input:focus-visible) {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.roster-search-input::-webkit-search-cancel-button {
  appearance: none;
  display: none;
}

/* Every interactive element and class. The element selectors catch the
   controls views build without a class; the classes name the app's own. */
a[href]:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[role="button"]:focus-visible,
[role="tab"]:focus-visible,
.btn-primary:focus-visible,
.btn-secondary:focus-visible,
.btn-danger:focus-visible,
.btn-icon:focus-visible,
.btn-row:focus-visible,
.menu-item:focus-visible,
.tab-btn:focus-visible,
.tab:focus-visible,
.chip:focus-visible,
.list-row button:focus-visible,
.heatmap-cell:not(.empty-cell):focus-visible,
.form-input:focus-visible,
.form-card-toggle:focus-visible,
.header-icon-btn:focus-visible,
.modal-close:focus-visible,
.modal-close-btn:focus-visible,
.log-details-btn:focus-visible,
.week-picker-trigger:focus-visible,
.sidebar-toggle:focus-visible,
.back-btn:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* The :focus { outline: none } rules above, restated for the keyboard at the
   same specificity. */
[data-auth-screen] input:focus-visible,
.invite-form input:focus-visible,
.feedback-form textarea:focus-visible,
.consent-form input:focus-visible,
.consent-form textarea:focus-visible,
.consent-form select:focus-visible,
.meeting-form input:focus-visible,
.meeting-form select:focus-visible,
.disclosure-picker-field input:focus-visible,
.disclosure-picker-field select:focus-visible,
.text-area-notes:focus-visible,
.slider-input:focus-visible,
.backfill-date-input:focus-visible,
.backfill-count-input:focus-visible,
.backfill-notes-input:focus-visible,
.meeting-reason-input:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* The switch's checkbox (.switch-input) is opacity: 0 and zero-sized; the
   track beside it (.switch-label) is what shows, so it takes the ring. */
.switch-input:focus-visible + .switch-label {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* The same for a dose, day or yes/no box: its checkbox is unseen, so the box
   after it takes the ring. */
.day-checkbox-input:focus-visible + .custom-checkbox-ui,
.boolean-checkbox-input:focus-visible + .custom-checkbox-ui {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* The guide's search field: guide.css (loaded after this file) sets
   `.guide-search:focus { outline: none }`, so the restatement needs one more
   class to win without moving it into that file. */
.guide-panel .guide-search:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* INSET, where a parent clips its overflow flush with the control, so an
   outside ring would be cut off: the sidebar destinations (.sidebar-menu,
   overflow-y: auto, the rows as wide as the menu), the segmented Cards / List
   halves (.segmented, overflow: hidden), the week arrows (.week-navigator,
   overflow: hidden), the underline tabs (.tabs-underline, overflow-x: auto)
   and a disclosure row that spans a list card (.list-card, overflow: hidden). */
.menu-item:focus-visible,
.segmented-option:focus-visible,
.nav-arrow-btn:focus-visible,
.week-navigator button:focus-visible,
.week-navigator .form-input:focus-visible,
.tabs-underline .tab:focus-visible,
.list-card summary:focus-visible {
  outline-offset: var(--focus-ring-inset);
}

/* The week picker's <input type="week"> is opacity: 0 over the date range
   (.hidden-week-input), so its own ring would be invisible: the visible
   .week-picker-trigger draws it instead, inset (.week-navigator clips). It
   keys on :focus-within, not :focus-visible, because Tab walks the input's
   week, year and calendar-button parts and Chrome reports :focus-visible (or
   even :focus) on the input for only some of them; a mouse click on the
   trigger opens the picker anyway. */
.week-picker-trigger:focus-within {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-inset);
}

/* ── R5 / W13 + W15: dialogs wrap long text and stay inside the visible area ──
   One block, at the end, so it sits below every per-dialog rule it must
   outrank and apart from the W14 dialog-chrome edits.
   W15: .modal-container is overflow-y:auto, which makes overflow-x auto too, so
   one unbroken word (a long medication name, a 100-character reference) widened
   the scroll area and the sheet panned sideways. The card now clips sideways,
   and the text it holds wraps: overflow-wrap is inherited, so this one rule
   covers every descendant that has no rule of its own. min-width:0 lets the
   title and the body shrink inside a flex row instead of growing to the word. */
.modal-container {
  min-width: 0;
  overflow-x: hidden;
  overflow-wrap: anywhere;
}

.modal-header h2,
.modal-body {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* A long email in Admin > People is cut with an ellipsis on one line (the full
   address is in the row's title). The separator no longer strands. */
.admin-resident-email {
  display: inline-block;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* Phone (and any touch screen this narrow): a title never shows without a
   pointer, so the cut address could not be read at all. Below 768px the email
   wraps instead, breaking anywhere, inside its row. Below the rule above. */
@media (max-width: 767px) {
  .admin-resident-email {
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

/* G7-009: the transport editor's section heads ("Stops", "Riders") have space
   above them and the form-label size, and the Days legend matches the other
   field labels (the global reset zeroes h5 margins and a legend keeps the
   browser's 16px). Last, so no earlier transport rule outranks them. */
.transport-editor h5 {
  margin: 20px 0 6px;
  font-size: var(--fs-h4);
  font-weight: 600;
  color: var(--text-secondary);
}

.transport-editor .transport-weekdays legend {
  padding: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
}

.transport-editor .transport-weekdays {
  margin-bottom: 16px;
}

.transport-editor .transport-weekdays label {
  font-size: 13px;
}

/* ── R6 / W7: form errors and focused fields stay clear of the pinned bars ──
   The title and the buttons are pinned (R5), so a field or an error scrolled
   "into view" could stop under either. scroll-padding tells the browser how
   much of the card they cover, and scrollIntoView and focus respect it. */
.modal-container {
  scroll-padding-top: 72px;
  scroll-padding-bottom: 96px;
}

/* On a phone the footer's buttons stack (about 137px: two 44px buttons, the gap
   and the card padding), so 96px of padding still let the field reached with the
   keyboard's Next key stop under Save (G7-038). Clear of the whole stack. */
@media (max-width: 767px) {
  .modal-container {
    scroll-padding-bottom: 160px;
  }
}

/* formErrors.js writes a list when there is more than one message. */
.form-error-list {
  margin: 0;
  padding-left: 16px;
}

/* The resident drawer's Out / Back code: a content-width button and 13px muted
   status, like the other drawer fields, not a full-column button (G1-032). */
[data-outing-code-group] {
  align-items: flex-start;
}

[data-outing-code-group] > [data-outing-code-status] {
  margin: 0;
  font-size: 13px;
}

/* The Out / Back code is hidden like a password but is a plain text box, so
   Chrome's password manager never fills it or offers to save it (G2-013). */
[data-masked-digits] { -webkit-text-security: disc; text-security: disc; }
/* Toast on a phone (W17). Above the pinned bottom bar (about 64px plus the
   safe area) inside a 16px gutter, so the page's primary and a sheet's footer
   button stay visible. Last, so it beats the desktop corner rule above. */
@media (max-width: 767px) {
  .toast-notification {
    left: 16px;
    right: 16px;
    bottom: calc(80px + env(safe-area-inset-bottom, 0px));
  }
}

/* Admin > Errors: the ID is read aloud and typed, so it is monospace. The search box and the
   per-route counts sit in a band under the card head; each row is the ID and time, then the route. */
.admin-error-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-primary);
}
.admin-error-tools {
  display: grid;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border-color-light);
}
.admin-error-search {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}
.admin-error-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
}
.admin-error-item {
  gap: 2px 12px;
  padding-top: 10px;
  padding-bottom: 10px;
}
.admin-error-item .feedback-at {
  justify-self: end;
}
.admin-error-what {
  grid-column: 1 / -1;
  font-size: 12px;
  color: var(--text-secondary);
}
/* Six tabs on a phone: two rows of three equal tabs (a sixth of 358px cannot
   hold Feedback and its count), and the draft pill keeps its number only.
   Since v2.05 only the operator has Release notes, so an ordinary admin's
   strip is three over two; the cells keep the same width either way. */
@media (max-width: 767px) {
  .admin-tab-pill-word {
    display: none;
  }
  .admin-tabs {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .admin-tabs .tab {
    padding-left: 2px;
    padding-right: 2px;
    gap: 4px;
    font-size: 13px;
  }
}

/* ── The setup wizard (setup wizard W2b) ────────────────────────────────────
   Desktop width only: under 900px the wizard is replaced by one sentence. A
   rule that sets `display` defeats the UA [hidden] rule, so each one below
   that can carry `hidden` restates it. */
.setup-wizard {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
.setup-narrow-note {
  display: none;
  margin: 0;
  padding: 16px;
}
.setup-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
/* One centred column for every wizard step (polish B2): titles, hints and
   buttons are centred; form labels and grid text stay left, where they read
   best. Every wizard-only rule is under .setup-wizard: Admin > Import renders
   .setup-body, .setup-panel, the stepper and the toolbar too, and keeps its
   own layout. */
.setup-wizard .setup-body {
  gap: 16px;
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
}
@media (max-width: 899px) {
  .setup-narrow-note {
    display: block;
  }
  .setup-body {
    display: none;
  }
}
.setup-stepper {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.setup-step {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  background: var(--bg-card);
  color: var(--text-secondary);
  font-size: 13px;
  font-weight: 500;
}
.setup-step.is-current {
  border-color: var(--color-brand);
  color: var(--text-primary);
  background: var(--color-brand-glow);
}
.setup-step.has-problems .setup-step-no {
  background: var(--text-danger);
  color: var(--color-brand-fg);
}
.setup-step.is-done .setup-step-no {
  background: var(--text-success);
  color: var(--color-brand-fg);
}
.setup-step-tick {
  color: var(--text-success);
  font-weight: 700;
}
.setup-step-no {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: var(--border-radius-full);
  background: var(--border-color);
  color: var(--text-primary);
  font-size: 12px;
}
.setup-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.setup-toolbar a.btn-secondary {
  text-decoration: none;
}
.setup-file {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.setup-saved {
  margin-left: auto;
}
.setup-wizard .setup-stepper,
.setup-wizard .setup-toolbar,
.setup-wizard .setup-grid-actions,
.setup-wizard .setup-footer {
  justify-content: center;
}
.setup-wizard .setup-toolbar,
.setup-wizard .setup-grid-actions,
.setup-wizard .setup-footer {
  gap: 8px 12px;
  margin: 0;
}
.setup-wizard .setup-saved {
  margin-left: 0;
  font-size: 13px;
}
.setup-wizard .setup-next-note,
.setup-wizard .setup-upload-note {
  margin: 0;
  text-align: center;
}
.setup-upload-note[hidden],
.setup-next-note[hidden] {
  display: none;
}
.setup-panel {
  min-width: 0;
  padding: 16px;
}
/* The wizard's panel stacks its parts with one gap, so no line can sit on the box below it. */
.setup-wizard .setup-panel {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 24px;
}
.setup-wizard .setup-panel > * {
  margin: 0;
}
/* The Facility form keeps its centring: same weight as the reset above, and after it. */
.setup-wizard .setup-panel > .setup-form {
  margin: 0 auto;
}
.setup-wizard .setup-panel > [data-setup-problems-box]:empty {
  display: none;
}
.setup-step-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  text-align: center;
}
.setup-step-head h3,
.setup-step-head p {
  margin: 0;
}
/* The Facility step: one labelled field per line in a narrow centred column. */
.setup-form {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
}
.setup-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.setup-field > label,
.setup-fieldset > legend,
.setup-slider label {
  color: var(--text-primary);
  font-size: 13px;
  font-weight: 500;
}
.setup-field-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
}
.setup-field-row .form-input {
  flex: 1 1 160px;
}
.setup-field-row .setup-num {
  flex: 0 0 88px;
  text-align: center;
}
.setup-field-row > .muted {
  font-size: 13px;
}
.setup-fieldset {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin: 0;
  padding: 16px;
  border: 1px solid var(--border-color-light);
  border-radius: var(--border-radius-md);
}
.setup-fieldset > legend {
  padding: 0 6px;
}
.setup-field-note,
.setup-field-error {
  margin: 0;
  font-size: 13px;
}
.setup-field-error {
  color: var(--text-danger);
}
.setup-field-note[hidden],
.setup-field-error[hidden] {
  display: none;
}
.setup-form .form-input[aria-invalid="true"] {
  border-color: var(--text-danger);
  box-shadow: inset 0 0 0 1px var(--text-danger);
}
/* The grid box scrolls inside itself both ways (polish A1). gridCells.js sets its
   max-height to the window space left, so the sideways scrollbar is always in view. */
.setup-grid-wrap {
  max-width: 100%;
  overflow: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
}
.setup-grid {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.setup-grid th,
.setup-grid td {
  padding: 2px;
  border-bottom: 1px solid var(--border-color-light);
  text-align: left;
  white-space: nowrap;
}
.setup-grid thead th {
  padding: 8px 6px;
  color: var(--text-secondary);
  font-weight: 600;
  background: var(--bg-card-hover);
}
.setup-grid .setup-rowno {
  width: 44px;
  padding: 2px 8px;
  color: var(--text-muted);
  text-align: right;
}
.setup-grid input,
.setup-grid textarea {
  width: 100%;
  min-width: 110px;
  padding: 6px;
  border: 1px solid transparent;
  border-radius: var(--border-radius-sm);
  background: var(--bg-input);
  color: var(--text-primary);
  font-size: 13px;
}
/* A multi-line cell (a History note): wider than the rest, grows downward, keeps its breaks. */
.setup-grid textarea {
  min-width: 260px;
  min-height: 32px;
  font-family: inherit;
  line-height: 1.35;
  resize: vertical;
  white-space: pre-wrap;
}
.setup-grid input:focus,
.setup-grid textarea:focus {
  border-color: var(--color-brand);
  background: var(--bg-input-focus);
}
.setup-grid input.is-bad,
.setup-grid textarea.is-bad {
  border-color: var(--text-danger);
  background: var(--bg-input);
  box-shadow: inset 0 0 0 1px var(--text-danger);
}
.setup-grid input[data-inherited]::placeholder {
  color: var(--text-muted);
  opacity: 1;
}
/* The header row and the Row column stay put while the box scrolls. */
.setup-grid thead th {
  position: sticky;
  top: 0;
  z-index: 2;
}
.setup-grid tbody .setup-rowno {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--bg-card);
}
.setup-grid thead .setup-rowno {
  left: 0;
  z-index: 3;
}
/* Set widths (gridCells.js): the columns hold them and long text scrolls inside its cell. */
.setup-grid.has-widths {
  min-width: 100%;
  table-layout: fixed;
}
.setup-grid.has-widths thead th {
  overflow: hidden;
  text-overflow: ellipsis;
}
.setup-grid.has-widths thead th:last-child {
  width: 96px;
}
.setup-grid.has-widths input,
.setup-grid.has-widths textarea {
  min-width: 0;
}
/* The drag handle on a header's right edge; also a keyboard control. */
.grid-resize {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 8px;
  cursor: col-resize;
  touch-action: none;
}
.grid-resize::after {
  content: "";
  position: absolute;
  top: 6px;
  right: 3px;
  bottom: 6px;
  width: 2px;
  border-radius: var(--border-radius-full);
  background: var(--border-color);
}
.grid-resize:hover::after,
.grid-resize:focus-visible::after {
  background: var(--color-brand);
}
.grid-resize:focus-visible {
  outline: 2px solid var(--color-brand);
  outline-offset: -2px;
}
/* A cell with a fixed list shows a small down mark at its right. */
.setup-grid input[data-grid-choice] {
  padding-right: 20px;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-muted) 50%), linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position: calc(100% - 13px) 50%, calc(100% - 9px) 50%;
  background-size: 4px 4px;
  background-repeat: no-repeat;
}
/* A cell's list: fixed to the window, so the grid box never clips it. */
.grid-listbox {
  position: fixed;
  z-index: 60;
  max-height: 240px;
  margin: 0;
  padding: 4px;
  overflow-y: auto;
  list-style: none;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-md);
  background: var(--bg-card);
  box-shadow: var(--shadow-md);
  font-size: 13px;
}
.grid-listbox[hidden] {
  display: none;
}
.grid-listbox [role="option"] {
  padding: 6px 10px;
  border-radius: var(--border-radius-sm);
  color: var(--text-primary);
  white-space: nowrap;
  cursor: pointer;
}
.grid-listbox [role="option"]:hover,
.grid-listbox [role="option"].is-active {
  background: var(--color-brand-glow);
}
.grid-listbox [role="option"][aria-selected="true"] {
  font-weight: 600;
}
/* Measures a column's longest value for a double-click fit. */
.grid-measure {
  position: absolute;
  visibility: hidden;
  padding: 0 6px;
  font-size: 13px;
  white-space: pre;
}
.setup-grid-actions {
  margin: 8px 0 0;
}
.setup-counts {
  margin: 8px 0 0;
}
.setup-wizard .setup-grid-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}
.setup-wizard .setup-counts {
  margin: 0;
  font-size: 13px;
}
/* ── Admin > Import (the importers share the wizard's grid) ── */
.import-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
.import-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 8px 0;
}
.import-pager[hidden] {
  display: none;
}
.import-left-out input {
  color: var(--text-muted);
  text-decoration: line-through;
}
.setup-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-brand);
  font-size: 13px;
  text-align: left;
  text-decoration: underline;
  cursor: pointer;
}
.setup-problems {
  margin: 12px 0 0;
  padding: 12px;
  border: 1px solid var(--text-danger);
  border-radius: var(--border-radius-md);
}
.setup-problems h4 {
  margin: 0 0 8px;
  color: var(--text-danger);
}
.setup-problems ul {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.setup-problems li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.setup-clear {
  padding: 4px 8px;
  font-size: 12px;
}
.setup-footer,
.setup-review-actions {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}
.setup-wizard .setup-review-actions {
  justify-content: center;
  gap: 12px;
}
.setup-next-note {
  margin: 0;
  text-align: right;
}
.setup-summary {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.setup-summary th,
.setup-summary td {
  padding: 8px;
  border-bottom: 1px solid var(--border-color-light);
  text-align: left;
}
.setup-confirm {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.setup-warning {
  color: var(--text-warning);
  text-align: center;
}
.setup-done {
  padding: 24px;
}

/* ── Settings: the Discipline tab (Phase 13 phase 2) ─────────────────────────
   The categories list is the shared list card: a column head and rows that
   share one set of columns (name, outcome, active, Up/Down). Only what no
   existing class gives is here. A phone drops the column head (.card-colhead
   already does) and stacks each row: the name across the top, then the rest. */
.discipline-card-top {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px 16px;
}

.discipline-cat-colhead,
.discipline-cat-row {
  --cols: minmax(0, 2fr) minmax(0, 1.2fr) 96px 132px;
}

.discipline-cat-active,
.discipline-cat-moves {
  display: flex;
  align-items: center;
  gap: 8px;
}

.discipline-cat-moves {
  justify-content: flex-end;
}

@media (max-width: 767px) {
  .discipline-cat-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }

  .discipline-cat-name {
    grid-column: 1 / -1;
  }
}

/* ══════════════════════════════════════════════════════
   PHASE 13 / PHASE 2: the write-up dialog and the Discipline tab.
   The dialog is a contentDialog (the phone bottom sheet comes from the
   dialog rules above); the outcome is the existing .segmented control. The
   tab reuses .glass-card, .list-card, .card-colhead, .table-row, .decision-strip
   and .row-menu; only what none of them gives is here. Every [hidden] is
   restated because each class below sets its own display.
   ══════════════════════════════════════════════════════ */
.writeup-form {
  display: flex;
  flex-direction: column;
}

.writeup-standing {
  margin-bottom: 12px;
}

.writeup-standing[hidden] {
  display: none;
}

.writeup-suggest {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 16px;
  padding: 10px 12px;
  border-radius: var(--border-radius-md);
  background-color: rgba(var(--marginal-rgb), 0.1);
  font-size: 13px;
}

.writeup-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 16px;
}

.writeup-when {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: 8px;
}

.writeup-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
}

.writeup-hint {
  font-size: 12px;
  color: var(--text-secondary);
}

.writeup-outcome {
  align-self: flex-start;
  flex-wrap: wrap;
}

.writeup-warning {
  margin: 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-warning);
}

.writeup-warning[hidden] {
  display: none;
}

.writeup-form textarea.form-input {
  resize: vertical;
  min-height: 72px;
  line-height: 1.4;
}

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

  .writeup-outcome {
    display: flex;
    align-self: stretch;
  }

  .writeup-outcome .segmented-option {
    flex: 1 1 auto;
    white-space: normal;
    text-align: center;
  }
}

.resident-discipline {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.resident-discipline [data-discipline-loading-failed] {
  color: var(--text-danger);
}

/* The suggestion banners are strips in one card, so the card carries no padding. */
.resident-record .discipline-suggestions {
  padding: 0;
  overflow: hidden;
}

.discipline-suggestions .decision-strip + .decision-strip {
  border-top: 1px solid var(--border-color-light);
}

.discipline-suggest-errors {
  margin: 0;
  padding: 8px 16px;
}

.discipline-suggest-errors.hidden {
  display: none;
}

/* The write-up list: one card, no padding, the head and rows share --cols. */
.resident-record .discipline-list {
  padding: 0;
  --cols: 128px 140px minmax(0, 1fr) auto 96px;
}

.resident-record .discipline-list.has-menu {
  --cols: 128px 140px minmax(0, 1fr) auto 96px 44px;
}

.discipline-row {
  align-items: start;
  padding-top: 12px;
  padding-bottom: 12px;
}

.discipline-when,
.discipline-outcome {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

.discipline-staff,
.discipline-resident {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.discipline-resident {
  margin-top: 4px;
  font-size: 12px;
  color: var(--text-secondary);
}

.discipline-row.is-dropped,
.discipline-row.is-dropped .discipline-resident {
  color: var(--text-muted);
}

.discipline-by {
  overflow-wrap: anywhere;
}

.discipline-menu {
  align-self: start;
}

@media (max-width: 767px) {
  .discipline-row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "when outcome"
      "category category"
      "what what"
      "by menu";
  }

  .discipline-row .discipline-when { grid-area: when; }
  .discipline-row .discipline-outcome { grid-area: outcome; align-items: flex-end; }
  .discipline-row .discipline-category { grid-area: category; font-weight: 600; }
  .discipline-row .discipline-what { grid-area: what; }
  .discipline-row .discipline-by { grid-area: by; }
  .discipline-row .discipline-menu { grid-area: menu; }
}

/* Phase 13 P3: contract dialogs
   The builder, the sign pad and the read-only view reuse .writeup-form,
   .toggle-row, .switch-label, .supplies-sig-pad, .resident-record-dl and the
   modal footer; only what none of them gives is here. Every [hidden] is
   restated because each class below sets its own display. */
.contract-form {
  gap: 4px;
}

.contract-section {
  margin: 14px 0 2px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-primary);
}

.contract-line-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.contract-line {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
}

.contract-line-remove[hidden] {
  display: none;
}

.contract-rule {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 10px 0 0;
  padding: 0;
  border: 0;
}

.contract-rule-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.contract-rule-body {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
}

.contract-rule-body[hidden],
.contract-rule-head [hidden] {
  display: none;
}

.contract-time {
  width: 140px;
}

.contract-meeting-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.contract-meeting {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.contract-meeting select {
  flex: 1 1 200px;
  min-width: 0;
}

.contract-extra {
  width: 72px;
}

.contract-radio,
.contract-pick {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  cursor: pointer;
}

.contract-pick-text {
  flex: 1 1 auto;
  min-width: 0;
}

.contract-picks {
  display: flex;
  flex-direction: column;
}

.contract-dates {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 16px;
}

.contract-lines {
  margin: 0 0 12px;
  padding-left: 20px;
}

.contract-lines li {
  margin: 4px 0;
}

.contract-history {
  margin: 4px 0 0;
  padding-left: 16px;
}

.contract-view-head {
  margin-bottom: 8px;
}

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

/* Phase 13 P3: contract card + discharge due */
.discharge-due-strip {
  margin: 0 0 12px;
  border: 1px solid var(--color-danger);
  border-radius: var(--radius-md, 8px);
  background-color: rgba(var(--non-compliant-rgb), 0.08);
}

.discharge-due-strip[hidden] {
  display: none;
}

.discipline-contract-actions {
  margin: 0;
  padding: 12px 0 0;
}

/* Phase 13 P5 C: the admin decision queue (Admin > Discipline). One card, one
   item a row, hairlines between; each item is a head, a context line, an
   optional quote and a strip with the question and the buttons. Below
   .glass-card, so its padding wins. */
.discipline-queue-summary {
  margin: 0 0 8px;
  font-size: 13px;
}

.discipline-queue-list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow: hidden;
}

.discipline-queue-item {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border-color-light);
  overflow-wrap: anywhere;
}

.discipline-queue-item:last-child {
  border-bottom: none;
}

.discipline-queue-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
}

.discipline-queue-name {
  font-size: 14px;
}

.discipline-queue-since {
  margin-left: auto;
  font-size: 12px;
}

.discipline-queue-context {
  margin: 0;
  font-size: 13px;
  color: var(--text-secondary);
}

.discipline-queue-quote {
  margin: 0;
  padding: 10px 12px;
  border-left: 0;
  border-radius: var(--border-radius-sm);
  background-color: var(--bg-input, rgba(127, 127, 127, 0.08));
  font-size: 13px;
  line-height: 1.45;
}

.discipline-queue-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
}

.discipline-queue-ask {
  flex: 1 1 220px;
  min-width: 0;
  font-weight: 500;
  font-size: 13px;
}

.discipline-queue-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
  /* A button label never breaks mid-word (the item's `anywhere` is for text). */
  overflow-wrap: normal;
}

@media (max-width: 767px) {
  .discipline-queue-actions {
    justify-content: flex-start;
    width: 100%;
  }
}
