/* Coat and sheet — design-system tokens and component classes.
   This file is the source of truth for the system's look: retune it here.
   The direction is described in docs/design/DIRECTION-coat-and-sheet.md. */
/* Use the operating system's native interface font immediately. Only the two
   numeric Plex Mono faces are downloaded and precached for offline use.
   Licence: SIL OFL 1.1, app/static/fonts/LICENSE.txt. */
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("../fonts/IBMPlexMono-Regular.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("../fonts/IBMPlexMono-Medium.woff2") format("woff2");
}

:root {
  /* Colour. The names are the roles the palette plays. Two materials make
     the system: the sheet and the coat.
     The sheet is the work surface — --color-bg is the ground, --color-surface
     a region raised off it, both a warm near-white, and --color-text a near-black ink at the
     muted strengths below.
     The coat is the chrome — the top bar, the icon rail and the footer are
     painted --color-coat, a roan's dark hair, and carry the mark's
     white flecks as --color-coat-text. Nothing else is ever coat-coloured:
     the coat is the frame, and the frame is how you know where the work is.
     --color-accent is the brand square and the one primary action: a clear
     vermilion accent, distinct from the red of act-now, the amber of
     attention, the green of settled and the blue of info, so the primary
     button never reads as a state.
     Warning and danger are semantic states, not palette entries: roan.css
     declares --state-act-* and --state-attention-* against DESIGN_RULES.md
     §2, and is the only file allowed to add one. A literal hex below this
     block is a mistake.

     Measured (docs/design/contrast.py): --color-bg label on --color-accent
     5.50:1; --color-accent on --color-surface 5.00:1; --color-text on
     --color-bg 17.58:1; --color-coat-text on --color-coat 16.86:1.
     DESIGN_RULES.md carries the full current table. */
  --color-bg: #faf9f6;
  --color-surface: #f0eeea;
  --color-text: #161310;
  --color-accent: #b63a29;
  --color-accent-2: #536678;
  --color-coat: #161310;
  --color-coat-text: #f7f4ee;
  /* The rules. Soft enough that a dense screen reads as calm, never dropped:
     the region rule keeps its 2px and its own stronger tone. */
  --color-divider: color-mix(in srgb, var(--color-text) 14%, transparent);
  --color-divider-strong: color-mix(in srgb, var(--color-text) 24%, transparent);

  /* Tonal ramps. Warm greys on one lightness scale; the accent ramp is the
     brand accent, the second ramp a slate that only tags use. */
  --color-neutral-100: #f2eee6;
  --color-neutral-200: #e4ded4;
  --color-neutral-300: #d1c8bb;
  --color-neutral-400: #b6ab9e;
  --color-neutral-500: #958b80;
  --color-neutral-600: #756d65;
  --color-neutral-700: #59514a;
  --color-neutral-800: #3c3631;
  --color-neutral-900: #211d1a;

  --color-accent-100: #fff0ec;
  --color-accent-200: #ffd6cc;
  --color-accent-300: #f2b0a4;
  --color-accent-400: #d96d5c;
  --color-accent-500: #b63a29;
  --color-accent-600: #9f2f20;
  --color-accent-700: #9f2f20;
  --color-accent-800: #732418;
  --color-accent-900: #4c1710;

  --color-accent-2-100: #eef1f4;
  --color-accent-2-200: #d9e0e6;
  --color-accent-2-300: #b9c5cf;
  --color-accent-2-400: #92a3b1;
  --color-accent-2-500: #6f8393;
  --color-accent-2-600: #536678;
  --color-accent-2-700: #3f5061;
  --color-accent-2-800: #2e3b48;
  --color-accent-2-900: #1f2833;

  /* Muted text. One ink at four strengths, every one of which paints text or
     an icon and so is held to 4.5:1 on the darker ground, --color-surface.
     Measured on --color-surface: 6.42 / 5.64 / 5.25 / 4.92; on --color-bg:
     6.73 / 5.84 / 5.44 / 5.10. Muted ink is for text and icons only — a fill
     is a state, and states live in roan.css. */
  --color-muted:        color-mix(in srgb, var(--color-text) 66%, transparent);
  --color-muted-strong: color-mix(in srgb, var(--color-text) 70%, transparent);
  --color-muted-soft:   color-mix(in srgb, var(--color-text) 64%, transparent);
  --color-muted-softer: color-mix(in srgb, var(--color-text) 62%, transparent);

  /* Ink on the coat. The fleck at full strength is --color-coat-text; a
     quieter word on the coat (a context label, the footer) is the fleck at
     70%, 8.56:1; the brand on the coat is the accent ramp's 300, the only
     accent tint used on the dark ground (10.15:1). The
     coat's rule is the fleck at 18%. */
  --color-coat-soft: color-mix(in srgb, var(--color-coat-text) 70%, transparent);
  --color-coat-accent: var(--color-accent-300);
  --color-coat-divider: color-mix(in srgb, var(--color-coat-text) 18%, transparent);

  /* The wash an interactive surface takes under the pointer and the press. */
  --wash-hover:  color-mix(in srgb, var(--color-text) 6%, transparent);
  --wash-active: color-mix(in srgb, var(--color-text) 12%, transparent);
  /* A tint of the accent, for the selected thing that is not a button:
     a chosen filter chip, the active pet card, the selected row. */
  --wash-accent: color-mix(in srgb, var(--color-accent) 14%, transparent);
  /* The same wash on the coat: a rail item under the pointer. */
  --wash-coat: color-mix(in srgb, var(--color-coat-text) 10%, transparent);

  /* Native interface type: regular text, medium labels and semibold headings. */
  --font-heading: var(--font-body);
  --font-heading-weight: 600;
  --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, "Helvetica Neue", Arial, sans-serif;
  --font-strong-weight: 600;
  --font-medium-weight: 500;
  /* Figures only: money, codes, batch numbers, the diary's time gutter and a
     headline number. Never prose. Plex Mono keeps these values aligned. */
  --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* Type scale. Fields stay at 16px on a phone to prevent iOS zooming
     the page when a user begins typing. */
  --text-3xs:   10px;   /* card kicker, avatar initial — chrome, never prose */
  --text-2xs:   11px;   /* uppercase labels, table headings, rail words */
  --text-xs:    12px;   /* metadata */
  --text-sm:    13px;   /* dense secondary text: card body, flashes, banners */
  --text-md:    14px;   /* controls, inputs, listing cells */
  --text-base:  15px;   /* body */
  --text-lg:    16px;   /* h5, and every form field below --bp-handheld */
  --text-xl:    19px;   /* nav brand, screen title */
  --text-2xl:   21px;   /* h4, dialog title */
  --text-3xl:   24px;   /* h3 */
  --text-4xl:   30px;   /* h2 */
  --text-5xl:   38px;   /* h1 */

  /* One size sits off the ramp, declared here so it can be seen: the card
     title, between --text-lg and --text-xl. */
  --text-card-title: 18px;

  /* Spacing scale. A 2px grid: whole steps 4px apart, half steps between. */
  --space-0-5:  2px;
  --space-1:    4px;
  --space-1-5:  6px;
  --space-2:    8px;
  --space-2-5: 10px;
  --space-3:   12px;
  --space-3-5: 14px;
  --space-4:   16px;
  --space-4-5: 18px;
  --space-5:   20px;
  --space-5-5: 22px;
  --space-6:   24px;
  --space-7:   28px;
  --space-8:   32px;

  /* Shape. Three radii, each a size of thing: 2px takes the edge off a chip,
     a tag or a key; 4px is a control — a button, a field, an appointment;
     6px is a region — a card, a flash, the rail's tab. Nothing is a pill.
     Rules and alignment still carry the structure. */
  --radius-sm: 2px;
  --radius-md: 4px;
  --radius-lg: 6px;

  /* Elevation. Almost none: one soft shadow for the rare thing that must
     float, nothing on cards or panels. */
  --shadow-sm: 0 1px 2px color-mix(in srgb, var(--color-neutral-900) 10%, transparent);
  --shadow-md: 0 3px 10px color-mix(in srgb, var(--color-neutral-900) 12%, transparent);
  --shadow-lg: 0 12px 32px color-mix(in srgb, var(--color-neutral-900) 18%, transparent);

  /* Motion. One duration, one curve. Cross-fades and washes only. */
  --motion-fast: 120ms;
  --motion-ease: cubic-bezier(.2, 0, 0, 1);
}

body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
}
h1, h2, h3, h4 { font-family: var(--font-heading); font-weight: var(--font-heading-weight); }

.grayscale{filter:grayscale(1) contrast(1.08)}

/* ══════════════════════════════════════════════════════════════════════════
   Components — built with the tokens above. Plain CSS on plain HTML: no
   JavaScript, no build step.
   ══════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-size: var(--text-base); line-height: 1.5; font-weight: 400;
       -webkit-font-smoothing: antialiased; }
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  line-height: 1.15; letter-spacing: -0.015em; margin: 0 0 var(--space-2);
}
h1 { font-size: var(--text-5xl); }
h2 { font-size: var(--text-4xl); }
h3 { font-size: var(--text-3xl); }
h4 { font-size: var(--text-2xl); }
h5 { font-size: var(--text-lg); }
h6 { font-size: var(--text-sm); }
h6 { letter-spacing: 0.08em; text-transform: uppercase; }
b, strong { font-weight: var(--font-strong-weight); }
p { margin: 0 0 var(--space-3); }
a { color: var(--color-accent); text-underline-offset: 3px; text-decoration-thickness: 1px;
    text-decoration-color: color-mix(in srgb, var(--color-accent) 45%, transparent); }
a:hover { text-decoration-color: currentColor; }
img { display: block; max-width: 100%; }
/* A fieldset groups controls for the reader; the browser's box around it is
   a card the system does not have, so the frame goes and the group is laid
   out like any other region. */
fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
legend { padding: 0; }
figure { margin: 0; }
figcaption {
  font-size: var(--text-2xs); margin-top: var(--space-1);
  color: var(--color-muted-soft);
}
.text-muted { color: var(--color-muted-soft); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px;
                 border-radius: var(--radius-sm); }
::selection { background: color-mix(in srgb, var(--color-accent) 25%, transparent); }

/* — rules — */
.hr {
  height: 2px; border: 0; margin: var(--space-4) 0;
  background: var(--color-divider-strong);
}

/* — buttons —
   Four voices, one size. Primary is the brand mulberry and there is one per
   screen; secondary is bordered on the ground; quiet is mulberry text for the
   action that should not compete; danger lives in roan.css because it is a
   state. Every button is at least --hit-min tall — there is no small button,
   only a compact one. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-1-5);
  cursor: pointer; text-decoration: none; white-space: nowrap;
  font-family: var(--font-body); font-weight: var(--font-strong-weight);
  font-size: var(--text-md); line-height: 1.2; color: var(--color-text);
  background: transparent; border: 1px solid transparent;
  padding: var(--space-2) calc(var(--space-3) * 1.2);
  border-radius: var(--radius-md);
  transition: background-color var(--motion-fast) var(--motion-ease),
              border-color var(--motion-fast) var(--motion-ease);
  /* The 36px floor from DESIGN_RULES.md §4; declared in roan.css, the layer
     that carries the product's metrics. */
  min-height: var(--hit-min);
}
.btn svg { display: block; width: 16px; height: 16px; flex: none; }
/* Disabled: the button goes grey and stays legible (muted-strong on
   neutral-200 is 5.71:1) rather than fading to a tint nobody can read. The
   reason it is held is written beside it — see .action-bar__consequence. */
.btn:disabled, .btn[aria-disabled="true"] {
  background: var(--color-neutral-200); border-color: var(--color-neutral-300);
  color: var(--color-muted-strong); cursor: not-allowed; pointer-events: none;
}
/* Loading: the label stays (say "Saving…" in the markup), a ring turns beside
   it, and the button stops taking clicks. */
.btn[aria-busy="true"] { pointer-events: none; opacity: 0.75; }
.btn[aria-busy="true"]::after {
  content: ""; width: 12px; height: 12px; flex: none; border-radius: 50%;
  border: 2px solid currentColor; border-right-color: transparent;
  animation: btn-spin 700ms linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .btn[aria-busy="true"]::after { animation: none; } }
/* The one primary action: brand mulberry, sheet-white label (8.35:1). Hover and
   press step down the accent ramp. */
.btn-primary { background: var(--color-accent); color: var(--color-bg); border-color: var(--color-accent); }
.btn-primary:hover { background: var(--color-accent-700); border-color: var(--color-accent-700); }
.btn-primary:active { background: var(--color-accent-800); }
.btn-secondary { border-color: var(--color-divider-strong); background: var(--color-bg); }
.btn-secondary:hover { background: var(--wash-hover); }
.btn-secondary:active { background: var(--wash-active); }
.btn-quiet, .btn-ghost { color: var(--color-accent); padding-inline: var(--space-2); }
.btn-quiet:hover, .btn-ghost:hover { background: var(--wash-accent); }
.btn-quiet:active, .btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }
/* Sizes. Compact keeps the 36px floor and loses side padding; large is for a
   sign-in or a till, where the one button is the screen. */
.btn-sm { font-size: var(--text-sm); padding-inline: var(--space-2-5); }
.btn-lg { font-size: var(--text-base); padding: var(--space-2-5) var(--space-4); min-height: 44px; }
.btn-icon { width: var(--hit-min); height: var(--hit-min); padding: 0; }
.btn-block { width: 100%; }
/* A row of buttons that belong together: the date stepper, a pair of filters. */
.btn-group { display: inline-flex; }
.btn-group > .btn + .btn { border-left-width: 0; }
.btn-group > .btn:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.btn-group > .btn:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; }
/* The chosen one of a row of buttons — the day in a week picker, a filter
   that is a link: the accent wash. Selected is not urgent, so never a state. */
.btn[aria-current="page"] { background: var(--wash-accent); color: var(--color-accent); border-color: var(--color-accent); }

/* — fields —
   One field is one .field: label above, control, then a hint or an error
   below. The control is ground-coloured with a 28% border and takes the
   accent border and caret on focus; the pure-white "being typed into"
   surface is declared in roan.css because it is a named product surface. */
.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.field > label:not(.check):not(.radio), .field__label {
  display: block; font-size: var(--text-xs);
  color: var(--color-muted-strong); font-weight: var(--font-medium-weight);
}
.field__hint { font-size: var(--text-xs); color: var(--color-muted-soft); margin: 0; }
.field__error { font-size: var(--text-xs); font-weight: var(--font-strong-weight); margin: 0; }
.input {
  width: 100%; min-height: var(--hit-min); padding: var(--space-1-5) var(--space-2-5); font: inherit;
  font-size: var(--text-md); color: var(--color-text); caret-color: var(--color-accent);
  background: var(--color-bg);
  border: 1px solid var(--color-divider-strong); border-radius: var(--radius-md);
  transition: border-color var(--motion-fast) var(--motion-ease),
              background-color var(--motion-fast) var(--motion-ease);
}
.input::placeholder { color: var(--color-muted-soft); }
.input:hover { border-color: color-mix(in srgb, var(--color-text) 40%, transparent); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
.input:disabled { opacity: 0.55; cursor: not-allowed; }
/* Read-only: on the surface tone, no focus surface, no caret. A value that
   is shown in a field so it lines up with the ones that can be changed. */
.input[readonly], .input--readonly {
  background: var(--color-surface); border-color: var(--color-divider); cursor: default;
}
textarea.input { min-height: 90px; resize: vertical; line-height: 1.5; }
/* A select keeps the browser's chevron unless it is wrapped in .select,
   which draws its own so the control matches the input beside it. */
.select { position: relative; display: block; }
.select > select.input { appearance: none; padding-right: calc(var(--space-2-5) * 2 + var(--space-3)); }
.select::after {
  content: ""; position: absolute; right: var(--space-3); top: 50%;
  width: 7px; height: 7px; pointer-events: none; color: var(--color-muted);
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: translateY(-70%) rotate(45deg);
}
/* A list box: as tall as its rows, no chevron, each option a hit target. */
select.input[multiple] { height: auto; padding: var(--space-1); overflow-y: auto; }
select.input[multiple] option { padding: var(--space-1-5) var(--space-2); border-radius: var(--radius-sm); }
select.input[multiple] option:checked { background: var(--wash-accent); color: var(--color-accent); }
.select:has(select[multiple])::after { display: none; }
/* A row of fields that closes with its button: the fields grow, a short
   one (a quantity, a code) holds back, and the button sits on the fields'
   baseline. Wraps on a phone. */
.field-row { display: flex; align-items: flex-end; gap: var(--space-2-5); flex-wrap: wrap; }
.field-row > .field { flex: 1 1 12rem; min-width: 0; }
.field-row > .field--short { flex: 0 1 8rem; }
/* A search field: the glass sits inside the control's left edge. */
.search { position: relative; display: block; }
.search > svg {
  position: absolute; left: var(--space-2-5); top: 50%; transform: translateY(-50%);
  width: 15px; height: 15px; pointer-events: none; color: var(--color-muted-soft);
}
.search > .input { padding-left: calc(var(--space-2-5) * 2 + 15px); }
.input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
/* A control that is a label with the input inside it: checkbox and radio.
   The native input is kept for the keyboard and the screen reader; the
   drawn .dot or .box is what the eye gets. */
.radio, .check {
  display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer;
  font-size: var(--text-md); min-height: var(--hit-min);
}
.radio input, .check input, .seg-opt input {
  position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
}
.radio .dot, .check .box {
  width: 16px; height: 16px; flex: none;
  border: 1.5px solid var(--color-divider-strong); background: var(--color-bg);
}
.radio .dot { border-radius: 50%; }
.check .box { border-radius: var(--radius-sm); display: grid; place-items: center; }
.radio:hover .dot, .check:hover .box { border-color: var(--color-accent); }
.radio input:checked + .dot {
  border-color: var(--color-accent); background: var(--color-accent);
  box-shadow: inset 0 0 0 4px var(--color-bg);
}
.check input:checked + .box { border-color: var(--color-accent); background: var(--color-accent); }
.check input:checked + .box::after {
  content: ""; width: 4px; height: 8px; margin-top: -2px;
  border-right: 2px solid var(--color-bg); border-bottom: 2px solid var(--color-bg);
  transform: rotate(45deg);
}
.radio input:focus-visible + .dot, .check input:focus-visible + .box {
  outline: 2px solid var(--color-accent); outline-offset: 2px;
}
.radio input:disabled ~ *, .check input:disabled ~ * { opacity: 0.55; }
/* Segmented control: one choice from a short row. Works as radios with
   inputs inside, or as links carrying aria-current="page". The chosen
   segment is cut from the coat, not the accent: it says where you are,
   and the accent is kept for the one thing to do next. */
.seg {
  display: inline-flex; flex-wrap: wrap;
  border: 1px solid var(--color-divider-strong); border-radius: var(--radius-md);
  background: var(--color-bg);
}
.seg-opt {
  display: inline-flex; align-items: center; gap: var(--space-1-5);
  min-height: var(--hit-min);
  padding: 7px var(--space-3); font-size: var(--text-sm); cursor: pointer;
  font-weight: var(--font-strong-weight); text-decoration: none; color: inherit;
}
.seg-opt + .seg-opt { border-left: 1px solid var(--color-divider); }
.seg-opt:has(input:checked), .seg-opt[aria-current="page"], .seg-opt[aria-pressed="true"] {
  background: var(--color-coat); color: var(--color-coat-text);
}
.seg-opt:not(:has(input:checked)):not([aria-current="page"]):not([aria-pressed="true"]):hover { background: var(--wash-hover); }
.seg-opt:has(input:focus-visible) { outline: 2px solid var(--color-accent); outline-offset: -2px; }
/* A form laid out in two columns at a desk and one on a phone. A field that
   needs the whole width says so with .form-grid__span. */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); align-items: start; }
.form-grid--1 { grid-template-columns: 1fr; }
.form-grid__span { grid-column: 1 / -1; }
.form-grid label:not(.radio):not(.check):not(.seg-opt) {
  display: block; margin-bottom: 5px; font-size: var(--text-xs); font-weight: var(--font-medium-weight);
  color: var(--color-muted-strong);
}
.form-grid .field > label { margin-bottom: 0; }
/* Keyboard key, as printed in a hint. */
.kbd {
  display: inline-block; padding: 0 5px; font-family: var(--font-mono); font-size: var(--text-2xs);
  line-height: 1.6; color: var(--color-muted); background: var(--color-surface);
  border: 1px solid var(--color-divider); border-bottom-width: 2px; border-radius: var(--radius-sm);
}

/* — cards and definition lists — */
.card {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-3); border-radius: var(--radius-lg); background: var(--color-surface);
  border: 1px solid var(--color-divider);
}
.card-kicker, .kicker {
  font-size: var(--text-3xs); letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent);
  font-weight: var(--font-medium-weight);
}
.card-title {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: var(--text-card-title); line-height: 1.2;
}
.card-body { margin: 0; font-size: var(--text-sm); flex: 1; }
.card-meta {
  display: flex; align-items: center; gap: var(--space-1-5); font-size: var(--text-2xs);
  color: var(--color-muted-soft);
}
.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }
/* Term and value, two columns, for the facts of a thing. */
.dl { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-1) var(--space-3); margin: 0; }
.dl dt { font-size: var(--text-xs); color: var(--color-muted); font-weight: var(--font-medium-weight); padding-top: 1px; }
.dl dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.dl--stack { grid-template-columns: 1fr; gap: var(--space-0-5); }
.dl--stack dt { margin-top: var(--space-2); }
.dl--stack dt:first-child { margin-top: 0; }

/* — tags — */
.tag {
  display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--text-2xs);
  letter-spacing: 0.02em; padding: 3px var(--space-2-5); font-weight: var(--font-medium-weight);
  border-radius: var(--radius-sm);
}
.tag svg { width: 12px; height: 12px; }
.tag-accent { background: var(--color-accent-100); color: var(--color-accent-800); }
.tag-accent-2 { background: var(--color-accent-2-100); color: var(--color-accent-2-800); }
.tag-neutral { background: var(--color-neutral-200); color: var(--color-neutral-800); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }

/* — navigation — */
.nav {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-2) var(--space-4);
  border-bottom: 2px solid var(--color-divider-strong);
}
.nav-brand {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: var(--text-lg); margin-right: auto; color: inherit;
}
.nav a { color: inherit; text-decoration: none; font-size: var(--text-md); }
.nav a:hover, .nav a[aria-current='page'] { text-decoration: underline; text-underline-offset: 6px; }
/* Where you are, as a path. The last crumb is the current thing and is not a
   link; the separators are drawn, not typed, so a screen reader skips them. */
.crumbs {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1-5);
  font-size: var(--text-sm); color: var(--color-muted);
}
.crumbs a { color: inherit; text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }
.crumbs > * + *::before { content: "/"; margin-right: var(--space-1-5); color: var(--color-muted-soft); }
.crumbs [aria-current="page"] { color: var(--color-text); }

/* — tables —
   One pattern for every listing below the handheld breakpoint, and it is a
   contained scroll region, not a stacked card view: the table keeps its shape,
   scrolls inside its own box rather than pushing the page sideways, says so
   with a shadow on its right edge that clears when the last column is on
   screen, and pins the first column — the name of the thing the row is about —
   while the rest moves. A stacked form needs the column heading carried on
   every cell (the `data-label` the ward sheet in roan.css uses), which is work
   per screen. Nothing is removed from the markup, so a screen reader reads
   every column either way, and desktop rendering is untouched: every rule for
   this sits inside the media query at the end of the section. */
.table { width: 100%; border-collapse: collapse; font-size: var(--text-md); }
.table th {
  text-align: left; font-size: var(--text-2xs); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--color-muted); font-weight: var(--font-medium-weight);
  padding: var(--space-2) var(--space-2-5); border-bottom: 2px solid var(--color-divider-strong);
  vertical-align: bottom;
}
.table td {
  padding: var(--space-2) var(--space-2-5);
  border-bottom: 1px solid var(--color-divider);
  vertical-align: top;
}
.table tbody tr:hover { background: var(--wash-hover); }
.table tr[aria-selected="true"] > td { background: var(--wash-accent); }
.table tr[data-muted="true"] > td { color: var(--color-muted-soft); }
.table td.actions, .table th.actions { text-align: right; }
.table td.actions .btn { margin-block: calc(var(--space-1) * -1); }
/* A figure cell: money, a code, a count. Mono, tabular, right-aligned, never
   wrapped. On a heading too, so the column lines up. */
.num { text-align: right; white-space: nowrap; }
td.num, .num:not(th) { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
/* The heading over a figure column lines up with the figures but keeps the
   heading's own face: mono is for the figures, not their name. */
th.num { text-align: right; }
/* A caption names the listing for the reader and the screen reader alike:
   above the table, in the panel head's voice, left with the first column. */
.table caption {
  caption-side: top; text-align: left; padding: var(--space-2) var(--space-2-5);
  font-size: var(--text-2xs); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--color-muted); font-weight: var(--font-medium-weight);
}
/* A heading that stays while the rows scroll under it. Attribute, not a
   class, so the shared listing's desk rendering stays untouched. */
[data-sticky-head] thead th { position: sticky; top: 0; z-index: 2; background: var(--color-bg); }
/* Column widths, for <col> or a heading. Five, and the rest is fluid. */
.w-xs { width: 80px; }
.w-sm { width: 110px; }
.w-md { width: 150px; }
.w-lg { width: 190px; }
.w-xl { width: 230px; }
/* A region that scrolls sideways inside its own box, at every width, with
   the same edge affordance as the listing. Mark it data-scroll-box too so
   an overflow audit knows the scrolling is intended. */
.scroll-box {
  overflow-x: auto; overscroll-behavior-x: contain; min-width: 0;
  background:
    linear-gradient(to left, var(--color-bg), transparent) 100% 0 / 24px 100% no-repeat local,
    linear-gradient(to left, var(--color-divider-strong), transparent) 100% 0 / 10px 100% no-repeat scroll;
}

/* 680px is --bp-handheld, declared with the other layout metrics in roan.css.
   A media query cannot read a custom property, so the number is written out
   here and tests/test_responsive_shell.py holds both sheets to the same two
   numbers. The ward sheet stacks instead, so it is left out of all of it. */
@media (max-width: 680px) {
  .table:not(.ward-table) {
    display: block; overflow-x: auto; overscroll-behavior-x: contain;
    /* The box is the containing block for anything positioned inside it, so
       a visually hidden heading cannot widen the page from off-screen. */
    position: relative;
    /* The affordance: a shadow on the right edge means there is more table
       that way; the first gradient travels with the columns and covers the
       second once the last column is on screen. */
    background:
      linear-gradient(to left, var(--color-bg), transparent) 100% 0 / 24px 100% no-repeat local,
      linear-gradient(to left, var(--color-divider-strong), transparent) 100% 0 / 10px 100% no-repeat scroll;
  }
  /* The caption rides above the scrolling box rather than inside it. */
  .table:not(.ward-table) caption { display: block; position: sticky; left: 0; }
  /* A cell keeps a readable width rather than folding a sentence into a
     column of single words: a fluid column is a phrase at least, and a cell
     that holds prose (a summary, a basis) is a line of it. The box scrolls;
     the words do not stack. */
  .table:not(.ward-table) th,
  .table:not(.ward-table) td { min-width: 7rem; }
  .table:not(.ward-table) td:is(.t-small, .t-clin, .t-body) { min-width: 14rem; }
  .table:not(.ward-table) th { white-space: nowrap; }
  /* The pinned first column is the name of the thing the row is about, and a
     name that wraps under itself is no longer a name. It holds its line; a
     note under the name may still wrap. */
  .table:not(.ward-table) td:first-child { white-space: nowrap; }
  .table:not(.ward-table) td:first-child :is(.t-meta, .t-small, div, p) { white-space: normal; }

  .table:not(.ward-table) th:first-child,
  .table:not(.ward-table) td:first-child { position: sticky; left: 0; z-index: 1; }
  .table:not(.ward-table) th:first-child {
    box-shadow: inset 0 -2px 0 var(--color-divider-strong), 1px 0 0 var(--color-divider);
  }
  .table:not(.ward-table) td:first-child {
    box-shadow: inset 0 -1px 0 var(--color-divider), 1px 0 0 var(--color-divider);
  }
  :where(.table:not(.ward-table) th:first-child,
         .table:not(.ward-table) td:first-child) { background: var(--color-bg); }
  /* A totals band (tfoot.totals: label and figure per line) is what gets
     read out across the desk, so it lies across the box and stays in view
     rather than scrolling off to the right with the columns it sums. */
  .table:not(.ward-table) .totals { display: block; position: sticky; left: 0; }
  .table:not(.ward-table) .totals tr { display: flex; justify-content: flex-end; align-items: baseline; }
  .table:not(.ward-table) .totals td { display: block; min-width: 0; }
  .table:not(.ward-table) .totals td:empty { padding: 0; }
  .table:not(.ward-table) .totals td:first-child { position: static; box-shadow: none; white-space: normal; }
  .form-grid { grid-template-columns: minmax(0, 1fr); }
}

/* — disclosure —
   A block that is folded until it is wanted: superseded versions, a form
   for the rare case. Native details/summary, so it works with no script;
   the summary is a hit target and draws its own chevron. Never for the
   thing the screen exists to show. */
summary {
  display: flex; align-items: center; gap: var(--space-2);
  min-height: var(--hit-min); cursor: pointer; font-weight: var(--font-strong-weight);
  list-style: none;
}
summary::-webkit-details-marker { display: none; }
summary::before {
  content: ""; width: 7px; height: 7px; flex: none; margin-inline: var(--space-0-5);
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--motion-fast) var(--motion-ease);
}
details[open] > summary::before { transform: rotate(45deg); }
summary:hover { color: var(--color-accent); }

/* — preformatted —
   Text whose line breaks are its own: a secret shown once, a command, dose
   arithmetic, a label as it will print. In the page's face unless it is a
   code, and always wrapping — nothing scrolls sideways for a line of text. */
.code-block {
  display: block; margin: 0; padding: var(--space-3) var(--space-3-5);
  background: var(--color-surface); font-family: var(--font-mono); font-size: var(--text-sm);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.working { margin: 0; white-space: pre-wrap; font: inherit; }
.label-preview {
  margin: 0; white-space: pre-wrap; font: inherit; padding: var(--space-2-5);
  border: 1px solid var(--color-divider); background: var(--color-surface);
}

/* — takeover —
   The product has no dialogs: nothing opens over anything else (HANDOFF,
   one canvas). A flow that would have been a modal is a full-canvas
   takeover — it replaces the screen, names itself, and always has a way
   back. .dialog is the same thing under its old name. */
.takeover, .dialog {
  display: flex; flex-direction: column; min-height: 100%;
  background: var(--color-bg);
}
.takeover__head {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  min-height: 52px; padding: var(--space-2) var(--space-3-5);
  border-bottom: 2px solid var(--color-divider-strong);
}
.takeover__title, .dialog-title {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: var(--text-2xl); margin: 0;
}
.takeover__body, .dialog-body { padding: var(--space-4) var(--space-3-5); max-width: 72ch; font-size: var(--text-md); }
.takeover__actions, .dialog-actions {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  padding: var(--space-2-5) var(--space-3-5); border-top: 2px solid var(--color-divider-strong);
  background: var(--color-surface);
}
.takeover__actions .btn-primary { margin-left: auto; }

/* — layout utilities —
   The few arrangements every screen writes, named once so a template never
   writes a style attribute for them. Margins step the spacing scale. */
.stack { display: flex; flex-direction: column; gap: var(--space-2); }
.stack--tight { gap: var(--space-1); }
.stack--loose { gap: var(--space-4); }
.row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; min-width: 0; }
.row--tight { gap: var(--space-1); }
.row--loose { gap: var(--space-3); }
.row--top { align-items: flex-start; }
.row--nowrap { flex-wrap: nowrap; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
.prose { max-width: 62ch; }
/* A form with one subject (raise a task, book an appointment) keeps a
   readable measure; a public sheet is centred and wide enough for a
   two-column form at a desk and the whole width of a phone. */
.form-single { max-width: 48rem; }
.sheet { width: min(56rem, 100%); margin: 0 auto; }
/* A list that is not bullets: appointments read back, the types on offer. */
/* A bulleted list in a tight block: the reasons a rota cannot be published. */
.bullets { margin: var(--space-1) 0 0; padding-left: var(--space-4); }
.plain-list { list-style: none; margin: 0; padding: 0; }
.plain-list > li + li { margin-top: var(--space-1); }
.plain-list > .region + .region { margin-top: 0; }
.u-grow { flex: 1 1 auto; min-width: 0; }
.u-min0 { min-width: 0; }
.u-ml-auto { margin-left: auto; }
.u-m0 { margin: 0; }
.u-mt-0-5 { margin-top: var(--space-0-5); }
.u-mt-1 { margin-top: var(--space-1); }
.u-mt-1-5 { margin-top: var(--space-1-5); }
.u-mt-2 { margin-top: var(--space-2); }
.u-mt-2-5 { margin-top: var(--space-2-5); }
.u-mt-3 { margin-top: var(--space-3); }
.u-mt-4 { margin-top: var(--space-4); }
.u-mb-1 { margin-bottom: var(--space-1); }
.u-mb-2 { margin-bottom: var(--space-2); }
.u-mb-3 { margin-bottom: var(--space-3); }
.u-mb-4 { margin-bottom: var(--space-4); }
.u-right { text-align: right; }
.u-left { text-align: left; }
.u-center { text-align: center; }
.u-full { width: 100%; }
.u-block { display: block; }
.u-inline { display: inline; }
.u-nowrap { white-space: nowrap; }
.u-break { overflow-wrap: anywhere; }
.u-strong { font-weight: var(--font-strong-weight); }
.u-normal-case { text-transform: none; letter-spacing: 0; }
.u-inherit { color: inherit; }
/* Print: the chrome is not part of a label or a receipt. */
.print-only { display: none; }
@media print {
  .no-print { display: none !important; }
  .print-only { display: revert; }
}
