/* Roster — clinical calm.
   Cool paper, scrubs-teal actions, and the shift-rhythm tricolor
   (morning gold / dusk amber / night slate) as the one loud thing.
   Both themes keep that identity: :root is light; [data-theme="dark"]
   (screen only — print always renders light) swaps every token for a
   deep blue-grey paper with the same tricolor rhythm. */

:root {
  color-scheme: light;
  --paper: #f5f8f9;
  --surface: #ffffff;
  --fg: #1b262d;
  --bg: #ffffff;            /* legacy alias: sticky cells, pickers */
  --muted: #5c6e78;
  --line: #dbe4e9;
  --line-strong: #becbd3;
  --accent: #0e7569;
  --accent-ink: #0a5a50;
  --accent-soft: #e3f0ee;
  --accent-contrast: #ffffff; /* text on accent-filled controls */
  --morning-bg: #fff4d9;
  --morning-line: #e3cd8f;
  --morning-ink: #6c5416;
  --evening-bg: #fbe9da;
  --evening-line: #e6be9a;
  --evening-ink: #7b4b1f;
  --night-bg: #233246;
  --night-ink: #d9e4f1;
  --night-dim: #a9bcd4;       /* ✕/lock buttons inside night chips */
  --night-dim-hover: #ffb4ab;
  --danger: #b3261e;          /* danger action surfaces */
  --danger-ink: #8f1e17;      /* danger hover */
  --danger-fg: #b3261e;       /* danger as text (split so dark can lighten) */
  --ok: #1a7f37;
  --ok-ink: #167030;          /* ok chip text on --ok-bg */
  --hover: #f2f6f7;           /* row hover wash */
  --row-tint: #fafcfc;        /* demand summary row */
  --weekend-tint: #e9f1f0;    /* grid weekend header */
  --weekend-soft: #eef4f3;    /* calendar weekend cells */
  --warn-bg: #fdf3d0;
  --warn-line: #ddc363;
  --warn-ink: #6f5a0d;
  --ok-bg: #e4f3e9;
  --ok-line: #9fd3ae;
  --bad-bg: #fbe9e7;          /* violations / red banner */
  --bad-line: #e2a19d;
  --bad-ink: #7a1c15;
  --bad-muted: #9c4f49;
  --lock: #b8912a;
  --leave-wash: #d9e1f0;      /* approved-leave hatching (grid + calendar) */
  --backdrop: rgba(15, 23, 30, 0.45);
  --shadow: 0 1px 2px rgba(23, 36, 44, 0.04);
  --shadow-pop: 0 6px 20px rgba(23, 36, 44, 0.16);
}

/* Dark: deep blue-grey paper (never pure black), slightly lighter surfaces,
   desaturated teal, shift tones as muted tinted backgrounds with legible
   ink; the night chip inverts to pale slate. Screen-only so printing from
   a dark session still yields the light wall roster. */
@media screen {
  :root[data-theme="dark"] {
    color-scheme: dark;
    --paper: #151c23;
    --surface: #1d2730;
    --fg: #dce5ec;
    --bg: #1d2730;
    --muted: #94a6b2;
    --line: #2c3a46;
    --line-strong: #475864;
    --accent: #4fbdae;
    --accent-ink: #79d0c3;
    --accent-soft: #17352e;
    --accent-contrast: #06201c;
    --morning-bg: #3a3114;
    --morning-line: #6d5a26;
    --morning-ink: #eedc9f;
    --evening-bg: #3f2d1c;
    --evening-line: #7a5836;
    --evening-ink: #f0c9a1;
    --night-bg: #ccdaeb;      /* inverted: pale slate on dark */
    --night-ink: #22313f;
    --night-dim: #5b6d84;
    --night-dim-hover: #a33228;
    --danger: #c2453c;
    --danger-ink: #d6564c;
    --danger-fg: #f0958e;
    --ok: #72c48c;
    --ok-ink: #8fd3a4;
    --hover: #232f3a;
    --row-tint: #19222b;
    --weekend-tint: #1f2b36;
    --weekend-soft: #1c2732;
    --warn-bg: #3c3413;
    --warn-line: #6a5a20;
    --warn-ink: #e7d283;
    --ok-bg: #1d3a27;
    --ok-line: #2f5c3c;
    --bad-bg: #46231e;
    --bad-line: #7c453f;
    --bad-ink: #f0a9a1;
    --bad-muted: #cd8b83;
    --lock: #d9b34e;
    --leave-wash: #34455f;    /* approved-leave hatching on dark paper */
    --backdrop: rgba(0, 0, 0, 0.55);
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-pop: 0 6px 20px rgba(0, 0, 0, 0.55);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  color: var(--fg);
  background: var(--paper);
  font-variant-numeric: tabular-nums;
}

/* --- Topbar: wordmark + the tricolor shift mark --- */

.topbar {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 0.65rem 1.2rem;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}

.topbar .brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 650;
  font-size: 1.02rem;
  letter-spacing: -0.01em;
  color: var(--fg);
  text-decoration: none;
}

/* The mark: three shift segments, morning → evening → night.
   Shared by the topbar brand and the login page's larger lockup. */
.mark {
  display: inline-flex;
  gap: 2px;
}
.mark i {
  width: 6px;
  height: 14px;
  border-radius: 2px;
}
.mark i:nth-child(1) { background: var(--morning-line); }
.mark i:nth-child(2) { background: var(--evening-line); }
.mark i:nth-child(3) { background: var(--night-bg); }

.topbar nav { display: flex; gap: 1.1rem; align-items: center; flex-wrap: wrap; }
.topbar nav a {
  color: var(--muted);
  text-decoration: none;
  padding: 0.15rem 0;
  border-bottom: 2px solid transparent;
  transition: color 120ms ease, border-color 120ms ease;
}
.topbar nav a:hover { color: var(--fg); border-bottom-color: var(--line-strong); }

/* Theme toggle + account menu, grouped at the far right of the topbar. */
.topbar-right {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.theme-toggle {
  background: none;
  color: var(--muted);
  border: 1px solid transparent;
  border-radius: 999px;
  min-width: 2.2rem;
  min-height: 2.2rem;
  padding: 0 0.4rem;
  font-size: 1rem;
  line-height: 1;
}
.theme-toggle:hover { background: var(--hover); color: var(--fg); }

/* Account menu: a native <details> dropdown so signing out is a deliberate
   two-step action (open → Sign out), not a single fat-fingerable click in
   the nav row. */
.usermenu { position: relative; }
.usermenu-trigger {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.2rem;
  min-height: 2.2rem;
  border-radius: 999px;
  color: var(--muted);
  border: 1px solid transparent;
}
.usermenu-trigger::-webkit-details-marker { display: none; }
.usermenu-trigger:hover,
.usermenu[open] .usermenu-trigger { background: var(--hover); color: var(--fg); }
.usermenu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 0.4rem);
  min-width: 9rem;
  padding: 0.3rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 0.5rem;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
  z-index: 50;
}
.usermenu-who {
  padding: 0.35rem 0.7rem 0.4rem;
  margin: 0 0 0.2rem;
  border-bottom: 1px solid var(--line);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}
.usermenu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.5rem 0.7rem;
  border-radius: 0.35rem;
  color: var(--fg);
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
}
.usermenu-item:hover { background: var(--hover); }
.usermenu-signout:hover { color: var(--danger-fg); }

main { padding: 1.4rem 1.2rem 3rem; max-width: 74rem; margin: 0 auto; }

/* --- Type --- */

h1 {
  font-size: 1.5rem;
  font-weight: 650;
  letter-spacing: -0.015em;
  margin: 0.2rem 0 1rem;
}
h1 .muted { font-weight: 450; font-size: 0.75em; letter-spacing: 0; }

h2, h3 {
  font-size: 0.8rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin: 0 0 0.7rem;
}
h3 { margin-top: 1.4rem; }

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

/* --- Cards --- */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 1.1rem 1.3rem;
  margin-bottom: 1.2rem;
  box-shadow: var(--shadow);
}

.card.narrow { max-width: 22rem; margin: 4rem auto; }

.card h1 { margin-top: 0; font-size: 1.25rem; }
.card h2 { margin-top: 0; }

/* Login lockup: the mark + wordmark above the sign-in card. */
.login-brand { text-align: center; margin-top: 3.2rem; }
.login-brand .mark { gap: 3px; }
.login-brand .mark i { width: 9px; height: 22px; border-radius: 3px; }
.login-brand .wordmark {
  font-size: 1.35rem;
  font-weight: 650;
  letter-spacing: -0.015em;
  margin-top: 0.45rem;
}
.login-brand .tagline { margin: 0.15rem 0 0; font-size: 0.9rem; }
.login-brand + .card.narrow { margin-top: 1.3rem; }

/* --- Forms --- */

label { display: block; margin-bottom: 0.8rem; }
label input, label select { display: block; width: 100%; margin-top: 0.25rem; }

/* Labelled controls sharing a flex row: split the width, line up with the button. */
.row label { flex: 1; margin-bottom: 0; font-size: 0.78rem; font-weight: 600; letter-spacing: 0.02em; color: var(--muted); }
.row label input, .row label select { font-size: 0.95rem; font-weight: 400; letter-spacing: 0; color: var(--fg); }

/* Paired controls under one label (e.g. cutoff date + time). */
label .pair { display: flex; gap: 0.3rem; margin-top: 0.25rem; }
label .pair input { margin-top: 0; flex: 1; width: auto; }

/* Wide labelled forms (e.g. new staff) wrap instead of squeezing. */
.row.wrap { flex-wrap: wrap; }
.row.wrap label { flex: 1 1 10rem; }
td .row { margin-top: 0.35rem; }
.row:has(label) { align-items: flex-end; }

input, select, textarea {
  padding: 0.42rem 0.55rem;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  font: inherit;
  background: var(--surface);
  color: var(--fg);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

details { margin: 0.6rem 0; }
details summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 0.85rem;
  user-select: none;
}
details[open] summary { margin-bottom: 0.4rem; }

/* Rosterable-managers prompts in the Staff card: linking an existing login to
   a rostered profile. .row-inline is the shared affordance — its summary reads
   as an action (used by the per-row "Link an existing login" picker, which
   stays a plain inline disclosure like its "Add login…" sibling). .addself and
   .unlinked additionally frame the two top-of-card prompts as quiet
   call-to-action boxes so they stand apart from the staff table. */
details.row-inline > summary { color: var(--accent-ink); }
details.addself,
details.unlinked {
  max-width: 34rem;
  padding: 0.55rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--row-tint);
}
details.addself > summary,
details.unlinked > summary { color: var(--fg); font-weight: 600; }
details.addself > .muted,
details.unlinked > .muted { margin: 0.45rem 0 0.3rem; }

/* --- Buttons --- */

button {
  padding: 0.42rem 0.95rem;
  border: none;
  border-radius: 6px;
  background: var(--accent);
  color: var(--accent-contrast);
  font: inherit;
  font-weight: 550;
  cursor: pointer;
  transition: background 120ms ease;
}
button:hover { background: var(--accent-ink); }
/* White text passes ≥4.5:1 on both themes' --danger (6.5:1 light, 5.0:1 dark). */
button.danger { background: var(--danger); color: #fff; }
button.danger:hover { background: var(--danger-ink); }

button.linklike {
  background: none;
  color: var(--muted);
  padding: 0;
  font-weight: 450;
}
button.linklike:hover { background: none; color: var(--fg); }

/* Quiet secondary action (e.g. the confirm dialog's Cancel). */
button.ghost {
  background: none;
  border: 1px solid var(--line-strong);
  color: var(--fg);
  font-weight: 450;
}
button.ghost:hover { background: var(--hover); }

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

/* Anchor styled as the primary action it is (e.g. "Generate roster"). */
a.btnlink {
  display: inline-block;
  padding: 0.32rem 0.8rem;
  border-radius: 6px;
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 550;
  text-decoration: none;
  transition: background 120ms ease;
}
a.btnlink:hover { background: var(--accent-ink); color: var(--accent-contrast); }

form.inline { display: inline; }
.row { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.8rem; }
.row input { flex: 1; }

/* Quick period-length buttons on the New period form. */
.length-presets { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.length-btn { font-size: 0.85rem; padding: 0.2rem 0.6rem; }

/* Per-period rule toggles panel. */
label.rule-toggle { display: inline-flex; align-items: center; gap: 0.35rem; font-weight: 400; margin: 0.15rem 0; }
.rule-pills { display: flex; flex-direction: column; gap: 0.3rem; margin: 0.4rem 0; }
.rule-pill-row { display: flex; align-items: center; gap: 0.5rem; }
.rule-pill-name { flex: 0 0 auto; }
.rule-pill-row .pill { margin-left: auto; }
a.editlink { display: inline-block; margin-bottom: 0.4rem; }

/* On/off pill toggle. */
.pill {
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  padding: 0.1rem 0.7rem;
  font-size: 0.85em;
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
}
.pill.pill-on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
.pill:disabled { cursor: default; opacity: 0.7; }

/* Rules editor rows. */
.rule-row { border: 1px solid var(--line); border-radius: 8px; padding: 0.6rem 0.8rem; margin: 0.5rem 0; }
.rule-row-head { display: flex; align-items: baseline; gap: 0.4rem; }
.rule-row-body { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; margin-top: 0.4rem; }
.rule-field { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.9em; }
.rule-field-wide { flex: 1 1 20rem; }
.rule-field-wide input { flex: 1; }
.rule-add { margin-top: 0.6rem; }
input.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85em; }

/* Per-type param fields (rulemeta.js `fields` specs): compact inline
   controls that keep the one-row-per-rule layout. */
.rule-param input[type="number"] { width: 5.5rem; }
.rule-param input[type="text"] { width: 9rem; }
.rule-param input.rule-list { width: 13rem; }
.rule-require { display: inline-flex; align-items: center; gap: 0.3rem; }
.rule-require input[type="number"] { width: 4rem; }
.rule-require input.rule-list { width: 12rem; }

/* New-hospital setup checklist. */
.checklist { border-color: var(--accent); border-width: 1.5px; }
.checklist ol { padding-left: 0.4rem; list-style: none; margin: 0; }
.checklist li { margin: 0.45rem 0; }
.checklist .step-state { display: inline-block; width: 1.3rem; color: var(--accent); font-weight: 650; }
.checklist li.done { color: var(--muted); }
.checklist li.done strong { font-weight: 500; }

/* --- Tables --- */

table { border-collapse: collapse; width: 100%; }
th, td { text-align: left; padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--line); }
thead th {
  color: var(--muted);
  font-weight: 600;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
tbody tr { transition: background 120ms ease; }
tbody tr:hover { background: var(--hover); }

/* --- Chips: the shift-rhythm language --- */

.chip {
  display: inline-block;
  padding: 0.08rem 0.55rem;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  font-size: 0.85em;
  white-space: nowrap;
  background: var(--surface);
}
.chip.morning { background: var(--morning-bg); border-color: var(--morning-line); color: var(--morning-ink); }
.chip.evening { background: var(--evening-bg); border-color: var(--evening-line); color: var(--evening-ink); }
.chip.night { background: var(--night-bg); color: var(--night-ink); border-color: var(--night-bg); }
.chip.nonclinical { background: transparent; border-style: dashed; color: var(--muted); }

/* Unfilled open slot: a deliberate gap offered to the ward. */
.chip.open-slot {
  border-style: dashed;
  border-color: var(--accent);
  color: var(--accent-ink);
  background: var(--accent-soft);
}

/* Inline ✕ remover inside a chip. */
.chip .linklike { font-size: 0.9em; line-height: 1; padding: 0 0.1rem; margin-left: 0.1rem; }
.chip .linklike:hover { color: var(--danger-fg); }

/* Approved-OT badge, prepended to the chip label. */
.otbadge {
  font-size: 0.7em;
  font-weight: 700;
  border: 1px solid currentColor;
  border-radius: 3px;
  padding: 0 2px;
  margin-right: 3px;
  letter-spacing: 0.02em;
}
.chip.night .linklike { color: var(--night-dim); }
.chip.night .linklike:hover { color: var(--night-dim-hover); }

/* Checkbox that stays inline in a labelled row (e.g. is_night). */
label.inline-check { display: flex; align-items: center; gap: 0.3rem; margin-bottom: 0; flex: 0 0 auto; }
label.inline-check input { display: inline; width: auto; margin-top: 0; }

.muted { color: var(--muted); }
.error { color: var(--danger-fg); }
.ok { color: var(--ok); }

.chip.warn { background: var(--warn-bg); border-color: var(--warn-line); color: var(--warn-ink); }
.chip.ok { background: var(--ok-bg); border-color: var(--ok-line); color: var(--ok-ink); }
.chip.bad { background: var(--bad-bg); border-color: var(--bad-line); color: var(--danger-fg); }
.active-row { background: var(--accent-soft); }
.active-row:hover { background: var(--accent-soft); }
.scroll-x { overflow-x: auto; }

/* Keep the leading column readable while a wrapped table scrolls
   (e.g. the /my shifts table on a phone). */
.scroll-x.sticky-first th:first-child,
.scroll-x.sticky-first td:first-child {
  position: sticky;
  left: 0;
  background: var(--surface);
}

/* Fairness table (fairness.js): sortable headings + per-column outliers. */
#fairness-table th.sortable { cursor: pointer; user-select: none; }
#fairness-table th.sortable:hover { color: var(--fg); }
#fairness-table th[aria-sort]::after { content: " ↓"; }
#fairness-table th[aria-sort="ascending"]::after { content: " ↑"; }
#fairness-table td.col-max { font-weight: 700; }
#fairness-table td.col-min { color: var(--muted); }
/* σ-outlier cells and the legend's example swatch reuse the app's warn
   tint (the same vars as .chip.warn, so light and dark are both covered). */
#fairness-table td.warn,
.fairness-legend .warn {
  background: var(--warn-bg);
  color: var(--warn-ink);
}
.fairness-legend .warn {
  border: 1px solid var(--warn-line);
  border-radius: 3px;
  padding: 0 0.25em;
}

/* Audit log: compact JSON details stay readable without stretching rows. */
td.audit-details {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8rem;
  overflow-wrap: anywhere;
  max-width: 26rem;
}

/* --- Toasts (ui.js) --- */

.toast-stack {
  position: fixed;
  bottom: 1rem;
  right: 1rem;
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: 24rem;
}
.toast {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--line-strong);
  border-left: 4px solid var(--accent);
  border-radius: 8px;
  padding: 0.6rem 0.5rem 0.6rem 0.8rem;
  box-shadow: var(--shadow-pop);
  animation: toast-in 160ms ease;
}
.toast-ok { border-left-color: var(--ok); }
.toast-error { border-left-color: var(--danger); }
.toast-msg {
  white-space: pre-wrap;      /* multi-line rule-violation lists */
  overflow-wrap: anywhere;
  font-size: 0.92rem;
}
.toast-close {
  background: none;
  color: var(--muted);
  padding: 0 0.3rem;
  font-weight: 400;
  line-height: 1.4;
  flex: 0 0 auto;
}
.toast-close:hover { background: none; color: var(--fg); }
.toast-action {
  flex: 0 0 auto;
  background: none;
  border: none;
  padding: 0 0.2rem;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
}
.toast-action:hover { color: var(--fg); }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(6px); }
}

/* --- Confirm dialog (ui.js, native <dialog>) --- */

dialog.confirm {
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  color: var(--fg);
  padding: 0;
  max-width: 26rem;
  box-shadow: var(--shadow-pop);
}
dialog.confirm::backdrop { background: var(--backdrop); }
.confirm-body { padding: 1.1rem 1.3rem; }
.confirm-msg {
  margin: 0 0 1rem;
  white-space: pre-wrap;      /* the ward-delete cascade text is long */
}
.confirm-actions { display: flex; justify-content: flex-end; gap: 0.5rem; }
.confirm-input { display: block; width: 100%; margin: 0 0 1rem; }

/* --- Roster grid --- */

table.grid th, table.grid td {
  border: 1px solid var(--line);
  padding: 0.15rem 0.25rem;
  min-width: 3.2rem;
  vertical-align: top;
  font-size: 0.85em;
}
table.grid thead th { text-align: center; letter-spacing: 0.02em; }
table.grid .month-row th {
  background: var(--paper);
  font-weight: 650;
  border-bottom-width: 2px;
  position: sticky;
  top: 0;
}
table.grid thead th.weekend { background: var(--weekend-tint); }
table.grid tbody th {
  text-align: left;
  white-space: nowrap;
  font-weight: 550;
  font-size: 0.8rem;
  text-transform: none;
  letter-spacing: 0;
  position: sticky;
  left: 0;
  background: var(--bg);
}
table.grid tbody tr:hover { background: transparent; }
table.grid .demand-row td { color: var(--muted); white-space: nowrap; background: var(--row-tint); }
table.grid td.violation { background: var(--bad-bg); }
/* Roving-tabindex focus ring for the editable grid (rostergrid.js) — same
   accent as the assignment picker's focus-visible ring. */
table.grid td[data-staff]:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Approved-leave shading (manager view only — the staff payload carries no
   leave data). A hatched wash as background-IMAGE, so the weekend/holiday
   background-color underneath stays visible; both themes via --leave-wash. */
table.grid td.leave-cell,
table.cal td.day.leave-cell {
  background-image: repeating-linear-gradient(135deg,
    var(--leave-wash) 0, var(--leave-wash) 4px, transparent 4px, transparent 10px);
}

/* Grid filters (toggles sweep): dimmed = filtered out, not removed. */
#grid-filters select, #grid-filters label { font-size: 0.9em; }
.filtered-out { opacity: 0.25; }
/* Hidden date column (role/name-search rows use the native `hidden`
   attribute instead — see rostergrid.js applyFilters). */
.col-hidden { display: none; }
/* table.grid.grid-compact beats the table.grid th/td rule above on
   specificity (0,2,2) vs (0,1,2) — .grid-compact alone would lose. */
table.grid.grid-compact td, table.grid.grid-compact th { padding: 1px 3px; font-size: 0.85em; }

.chip.asg { display: inline-flex; align-items: center; gap: 2px; margin: 1px 0; }
.chip.locked { border-color: var(--lock); box-shadow: inset 0 0 0 1px var(--lock); }
.chipbtn {
  background: none;
  color: var(--muted);
  border: none;
  padding: 0 2px;
  font-size: 0.9em;
  cursor: pointer;
  display: none;
}
.chip.asg:hover .chipbtn { display: inline; }
.chip.night .chipbtn { color: var(--night-dim); }
.addbtn {
  background: none;
  color: var(--line-strong);
  border: 1px dashed var(--line-strong);
  border-radius: 4px;
  padding: 0 0.35rem;
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease;
}
td:hover .addbtn { color: var(--accent); border-color: var(--accent); }
.picker {
  position: absolute;
  z-index: 10;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: var(--shadow-pop);
  padding: 0.3rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.picker button { background: none; color: var(--fg); text-align: left; font-weight: 450; }
.picker button:hover { background: var(--accent-soft); }
.picker button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
td { position: relative; }

/* Shortfall banner: the roster page's one-line verdict on rule breaks. */
.banner {
  border: 1px solid var(--bad-line);
  background: var(--bad-bg);
  color: var(--bad-ink);
  border-radius: 10px;
  padding: 0.8rem 1.1rem;
  margin: 0.8rem 0 1.2rem;
}
.banner strong { display: block; margin-bottom: 0.2rem; }
.banner .muted { color: var(--bad-muted); }
/* Amber "heads up" variant (draft-roster notice), distinct from the red
   violations banner above. */
.banner.notice { border-color: var(--warn-line); background: var(--warn-bg); color: var(--warn-ink); }
.banner.notice strong { color: var(--warn-ink); }
/* Accent "working" variant (solver in progress), calm rather than alarming. */
.banner.solving { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-ink); }
.spinner {
  display: inline-block;
  width: 0.9em;
  height: 0.9em;
  margin-right: 0.45em;
  vertical-align: -0.1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spinner-turn 0.8s linear infinite;
  /* Under prefers-reduced-motion the global rule freezes this into a
     static broken ring; the banner text still says what's happening. */
}
@keyframes spinner-turn { to { transform: rotate(360deg); } }

/* --- Calendar view --- */

table.cal { table-layout: fixed; min-width: 56rem; }
table.cal th { text-align: center; }
table.cal th.wk { width: 2.6rem; color: var(--muted); font-weight: 500; vertical-align: top; }
table.cal td.day {
  border: 1px solid var(--line);
  vertical-align: top;
  height: 5.5rem;
  padding: 0.25rem 0.35rem;
  font-size: 0.8em;
}
table.cal td.day.off { background: var(--paper); }
table.cal td.day.weekend { background: var(--weekend-soft); }
table.cal td.day.holiday { background: var(--morning-bg); }
.cal .day-head { display: flex; gap: 0.4rem; align-items: baseline; margin-bottom: 0.15rem; }
.cal .day-num { font-weight: 650; }
.cal .holiday-name { font-size: 0.85em; }
.cal .shift-line { margin: 0.12rem 0; line-height: 1.35; }
.cal .shift-line .names { color: var(--fg); }
.cal td.day.editable { cursor: pointer; }
.cal td.day.editable:hover { box-shadow: inset 0 0 0 2px var(--accent-soft); }
.cal .picker { min-width: 20rem; cursor: default; }
.cal .picker .row { margin-top: 0.4rem; }
.cal .picker select { max-width: 10rem; }

/* --- Lists (requests, dashboard) --- */

ul { padding-left: 1.2rem; }
li { margin: 0.25rem 0; }

/* Dashboard attention panel: what needs a manager's action, per hospital. */
ul.attention { margin: 0.2rem 0 0.6rem; font-size: 0.92em; }
.attention-none { margin: 0.1rem 0 0.5rem; font-size: 0.92em; }

/* --- Small screens ---
   The staff-facing surfaces (/my, read-only roster views, periods, help,
   login, dashboard) get first-class phone treatment; manager-heavy editing
   surfaces stay desktop-first. */

@media (max-width: 640px) {
  main { padding: 1rem 0.8rem 2.5rem; }
  .topbar { flex-wrap: wrap; gap: 0.4rem 1.1rem; padding: 0.55rem 0.9rem; }
  .topbar nav { gap: 1rem; }
  .topbar nav a { padding: 0.55rem 0; } /* ≥40px tap targets */
  .row { flex-wrap: wrap; }
  .card { padding: 0.9rem 1rem; }
  .card.narrow { margin: 2rem auto; }

  /* ≥16px inputs prevent iOS focus zoom; comfortable touch buttons. */
  input, select, textarea, .row label input, .row label select { font-size: 16px; }
  button { min-height: 40px; }
  button.linklike, button.ghost, .chipbtn, .addbtn, .pill, .toast-close { min-height: 0; }
  /* Account menu trigger is a <summary>, not a <button> — size it for touch. */
  .usermenu-trigger { min-height: 40px; min-width: 40px; }
  select { max-width: 100%; }
  .row > * { min-width: 0; }

  /* Wall calendar keeps scroll-x but needs less runway on a phone. */
  table.cal { min-width: 40rem; }
  .cal .picker { min-width: 0; max-width: 88vw; }
  .cal .picker select { max-width: 100%; }

  /* Toasts span the bottom edge. */
  .toast-stack { left: 0.75rem; right: 0.75rem; bottom: 0.75rem; max-width: none; }
  dialog.confirm { max-width: calc(100vw - 2rem); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* --- Print: the roster page becomes a wall roster ---
   The dark theme's tokens are scoped to @media screen above, so print
   always renders with the light palette regardless of the chosen theme.
   Scoped to the roster page (the only page with #grid-card): everything
   but the VISIBLE view card — calendar or grid, whichever isn't [hidden] —
   is suppressed, so printing while the Calendar view is open yields a
   clean wall calendar. Other pages print unchanged. */

@media print {
  body:has(#grid-card) { background: #fff; }
  body:has(#grid-card) .topbar,
  body:has(#grid-card) #actions,
  body:has(#grid-card) .banner,
  body:has(#grid-card) .card { display: none; }
  body:has(#grid-card) main { max-width: none; padding: 0; }
  /* Re-show the view cards; the [hidden] one stays hidden (higher
     specificity than the bare id, which outranks the UA [hidden] rule). */
  body:has(#grid-card) #calendar-card,
  body:has(#grid-card) #grid-card { display: block; border: none; box-shadow: none; padding: 0; }
  body:has(#grid-card) #calendar-card[hidden],
  body:has(#grid-card) #grid-card[hidden] { display: none; }
  /* The everyone/one-person filter row is a screen control. */
  body:has(#grid-card) #calendar > .row { display: none; }
  /* Grid filters (role/area/shift toggles) are a screen control; filtered/dimmed
     state prints WYSIWYG just like the name-search behavior. The unfilled-only
     empty-state note is a sibling of the filter row (not a child), so it needs
     its own hide — it narrates a screen control, not the roster. */
  body:has(#grid-card) #grid-filters,
  body:has(#grid-card) #unfilled-note { display: none; }
  body:has(#grid-card) .scroll-x { overflow: visible; }

  /* Screen chrome that never belongs on paper. */
  .toast-stack, dialog.confirm, .theme-toggle { display: none; }

  /* Wall-calendar table: full width, black rules on white, keep the light
     shift-tone tints, and never split a week row across pages. */
  table.cal { min-width: 0; width: 100%; }
  table.cal th, table.cal td.day { border: 1px solid #000; }
  table.cal tr { break-inside: avoid; page-break-inside: avoid; }
  table.cal, table.grid {
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }
}
