/* Mission Control — modern operator console.
   Neutral cool-grey world (as before), rebuilt as a modern UI: a layered elevation system,
   one accent used only for meaning, a proper component vocabulary with every interactive
   state, and a tight typographic rhythm. No build step — plain CSS by product constraint.

   Palette is the original neutral: cool archive greys with a single blue ink for the
   machine's voice and the current selection, red for fault, green for settled work.
   Light is primary; dark is the desk's stored option. */

:root {
  color-scheme: light;   /* the default: light, until the operator says otherwise */

  /* surfaces, layered: ground → panel → raised → sunken */
  --bg:        #eef0ef;
  --surface:   #fbfcfb;
  --raised:    #ffffff;
  --sunken:    #f2f4f3;
  --hover:     #f0f2f1;

  /* type */
  --ink:       #14171b;
  --ink-2:     #454a51;
  --ink-3:     #5f656d;
  /* #6e757d measured 4.07:1 on --bg and 4.22:1 on --sunken — below the AA floor for the
     small metadata sizes this token mostly sets. #656b73 holds ≥4.7:1 on all four light
     surfaces and keeps a visible step below --ink-3 (#5f656d, ≥5.1:1). */
  --ink-4:     #656b73;

  /* lines */
  --line:      #dfe3e2;
  --line-2:    #cbd1cf;
  --line-3:    #b0b7b4;

  /* one accent, used for meaning only */
  --accent:      #1b4d8f;
  --accent-hi:   #163f76;
  --accent-soft: #e8eff9;
  --accent-line: #b9cee9;

  --ok:        #1f6b45;
  --ok-soft:   #e6f2ea;
  --ok-line:   #a9cdb8;

  --warn:      #8a5a0b;
  --warn-soft: #fbf0dc;
  --warn-line: #e3c78f;

  --danger:      #a32a1f;
  --danger-hi:   #8a231a;
  --danger-soft: #fbeae7;
  --danger-line: #e8b7b0;

  --ui: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;
  /* the rail holds a case name, a state chip and a two-line destination — at the desk's 110%
     scale it needs the room, or the destination is ellipsised to nothing */
  --rail: 380px;

  --r-sm: 6px; --r-md: 8px; --r-lg: 12px; --r-xl: 16px;
  --r-pill: 999px;

  /* elevation: offset + soft blur, never a halo */
  --e1: 0 1px 2px rgba(20, 23, 27, 0.06);
  --e2: 0 2px 6px rgba(20, 23, 27, 0.08), 0 1px 2px rgba(20, 23, 27, 0.04);
  --e3: 0 8px 24px rgba(20, 23, 27, 0.10), 0 2px 6px rgba(20, 23, 27, 0.05);
  --e4: 0 24px 56px rgba(20, 23, 27, 0.20), 0 6px 16px rgba(20, 23, 27, 0.08);

  --ring: 0 0 0 3px rgba(27, 77, 143, 0.18);
  --dur: 150ms;
  --ease: cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* The dark palette applies when the operator chose dark, OR when they are on "system" and the OS
   says dark. One palette, two triggers — the colours are never duplicated. */
[data-theme="dark"],
[data-theme="system"][data-prefers="dark"] {
  color-scheme: dark;

  --bg:        #16191c;
  --surface:   #1c2024;
  --raised:    #232830;
  --sunken:    #191d21;
  --hover:     #262b30;

  --ink:       #eef0f2;
  --ink-2:     #b7bdc4;
  --ink-3:     #9aa1a9;
  --ink-4:     #868d95;

  --line:      #2a3037;
  --line-2:    #363d45;
  --line-3:    #4a525b;

  --accent:      #7dabef;
  --accent-hi:   #93bbf4;
  --accent-soft: #1a2534;
  --accent-line: #2f4a6d;

  --ok:        #6fc094;
  --ok-soft:   #14271d;
  --ok-line:   #2c5140;

  --warn:      #e0b467;
  --warn-soft: #2a2113;
  --warn-line: #55492c;

  --danger:      #ef897a;
  --danger-hi:   #f5a396;
  --danger-soft: #2b1a17;
  --danger-line: #5d332c;

  --e1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --e2: 0 2px 6px rgba(0, 0, 0, 0.45), 0 1px 2px rgba(0, 0, 0, 0.3);
  --e3: 0 8px 24px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);
  --e4: 0 24px 56px rgba(0, 0, 0, 0.6), 0 6px 16px rgba(0, 0, 0, 0.4);

  --ring: 0 0 0 3px rgba(125, 171, 239, 0.24);
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
.defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* visually hidden, still announced */
.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;
}
/* keyboard-only skip link, revealed on focus */
.skip {
  position: absolute; left: var(--s3); top: -48px; z-index: 100;
  background: var(--accent); color: #fff; font-size: 13px; font-weight: 600;
  padding: 8px 14px; border-radius: var(--r-md); text-decoration: none;
  transition: top var(--dur) var(--ease);
}
.skip:focus { top: var(--s3); }

/* --------------------------------------------------------------------- sizing
 *
 * The desk reads at 110% of its authored size. Everything is authored in px, so the zoom is
 * a single root-level scale rather than a pass over 380 measurements: `zoom` on :root scales
 * the whole layout — text, controls, spacing, the grid — and the browser recomputes the
 * viewport in the same units, so nothing overflows and nothing gets clipped at the edges.
 *
 * 110% was asked for; --zoom is the one number to change if that changes.
 */
:root { zoom: var(--zoom, 1.1); }

html, body { height: 100%; }
/* An app shell: the desk owns every scrollbar (the rail and the sheet each scroll inside
   themselves), so the page itself never scrolls. Without this the browser's own scrollable
   area picks up the height of the long step card under `zoom` and the whole console can be
   dragged off-screen even though every panel is correctly contained. */
html, body { overflow: hidden; }
body {
  margin: 0;
  font-family: var(--ui);
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--bg);
  display: grid;
  grid-template-rows: auto 1fr;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  touch-action: manipulation;
}
.mono, input, pre, textarea { font-family: var(--mono); font-feature-settings: "tnum" 1, "zero" 1; }

::selection { background: var(--accent-soft); color: var(--ink); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-sm); }
a { color: var(--accent); text-underline-offset: 3px; }

h1, h2, h3 { text-wrap: balance; }

/* ------------------------------------------------------------- top bar */

.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s5);
  padding: 0 var(--s4);
  height: 56px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: relative;
  z-index: 10;
}
.mark { display: flex; align-items: center; gap: var(--s3); min-width: 0; cursor: pointer; border-radius: var(--r-md); padding: 6px 10px; margin: -6px 0 -6px -10px; transition: background var(--dur) var(--ease); }
.mark:hover { background: var(--hover); }
.mark-rose { width: 26px; height: 26px; flex: none; color: var(--accent); opacity: .85; }
.mark-text { display: flex; align-items: baseline; gap: var(--s3); min-width: 0; }
.mark-name { font-weight: 680; letter-spacing: -0.02em; font-size: 15px; }
.mark-sub {
  font-size: 13px; color: var(--ink-3);
  padding-left: var(--s3); border-left: 1px solid var(--line-2);
  white-space: nowrap;
}
.bar-meta { display: flex; align-items: center; gap: var(--s5); font-size: 13px; }
.kv { display: inline-flex; align-items: baseline; gap: 6px; }
.kv .k {
  color: var(--ink-3); text-transform: uppercase;
  letter-spacing: 0.07em; font-size: 11px; font-weight: 600;
}
.kv b { font-weight: 620; font-family: var(--mono); font-size: 13px; color: var(--ink); }
.bar-tools { display: inline-flex; align-items: center; gap: var(--s1); }

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

.btn {
  font: inherit;
  font-size: 13px;
  font-weight: 560;
  letter-spacing: -0.005em;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  padding: 0 12px;
  height: 32px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  white-space: nowrap;
  box-shadow: var(--e1);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}
.btn:hover:not(:disabled) { background: var(--hover); border-color: var(--line-3); }
.btn:active:not(:disabled) { transform: translateY(1px); box-shadow: none; }
.btn:disabled {
  color: var(--ink-4); background: var(--sunken);
  border-color: var(--line); cursor: not-allowed; box-shadow: none;
}
.btn.primary {
  background: var(--accent); border-color: var(--accent); color: #fff;
  box-shadow: var(--e1), inset 0 1px 0 rgba(255,255,255,0.10);
}
.btn.primary:hover:not(:disabled) { background: var(--accent-hi); border-color: var(--accent-hi); }
.btn.primary:disabled { background: var(--sunken); border-color: var(--line); color: var(--ink-4); box-shadow: none; }
.btn.ghost {
  border-color: transparent; background: transparent; color: var(--ink-2);
  box-shadow: none; padding: 0 10px;
}
.btn.ghost:hover:not(:disabled) { background: var(--hover); border-color: transparent; color: var(--ink); }
.btn.danger { color: var(--danger); background: var(--raised); border-color: var(--line-2); }
.btn.danger:hover:not(:disabled) { background: var(--danger-soft); border-color: var(--danger-line); color: var(--danger); }
.btn.danger-solid { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn.danger-solid:hover:not(:disabled) { background: var(--danger-hi); border-color: var(--danger-hi); }
.btn.icon { padding: 0; width: 32px; }
.btn.sm { height: 28px; font-size: 13px; padding: 0 10px; }
.glyph-svg { width: 15px; height: 15px; flex: none; }

/* ------------------------------------------------------------------ desk */

.desk {
  display: grid;
  grid-template-rows: auto 1fr;
  min-height: 0;
  overflow: hidden;
  background: var(--bg);
}
.col { min-height: 0; overflow: auto; }
/* the two columns' content sat flush on their own borders (the audit's "cramped padding" on
   `section.col`): the head already owns its bottom hairline, but the queue's rows began
   immediately on the right border and the sheet's card on the top-left corner. A small inner
   gutter on both gives every column's content its own air without moving either border. */
.queue { padding-left: 1px; }
.sheet { padding: 1px 0 0 1px; }

/* the rail sits on the sunken surface, separated by a hairline */
.queue {
  background: var(--surface);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.col-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: 0 var(--s4) var(--s2) var(--s4);
  min-height: 44px;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: 2;
  flex: none;
}
.col-head h2 {
  margin: 0;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-3);
  font-weight: 660;
}

.sheet {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface);
}
.sheet-scroll {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}

/* -------------------------------------------------------------- cause list */
.rail-nav { display: flex; gap: 2px; padding: var(--s2) var(--s3) 0; }
.rail-tab {
  flex: 1; font: inherit; font-size: 13px; font-weight: 620;
  padding: 7px 10px; border-radius: var(--r-sm);
  color: var(--ink-2); background: transparent; border: 1px solid transparent;
  cursor: pointer; transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.rail-tab:hover { background: var(--hover); color: var(--ink); }
.rail-tab.on {
  color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line);
}

.queue-list { list-style: none; margin: 0; padding: var(--s2); display: flex; flex-direction: column; gap: 2px; }
.entry {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr);
  gap: 0 var(--s2);
  align-items: start;
  width: 100%;
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: 8px 10px;
  cursor: pointer;
  position: relative;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.entry:hover { background: var(--hover); }
.entry[aria-current="true"] {
  background: var(--accent-soft);
  border-color: var(--accent-line);
}
.entry-idx {
  font-family: var(--mono); font-size: 12px; color: var(--ink-4);
  text-align: right; padding-top: 3px;
  font-feature-settings: "tnum" 1; font-weight: 500;
  grid-row: 1 / span 2;
}
.entry[aria-current="true"] .entry-idx { color: var(--accent); font-weight: 700; }
.entry-main { min-width: 0; grid-column: 2; display: block; }
.entry-name {
  display: block; font-weight: 600; font-size: 13px; line-height: 1.35;
  overflow-wrap: anywhere;
  /* one line, ellipsised — the rail is a scannable index, not a document */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.entry-meta {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 6px 8px; margin-top: 4px; min-width: 0;
}
.entry-id {
  font-size: 11px; color: var(--ink-4); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.entry-sub {
  display: block; font-size: 12px; color: var(--ink-3);
  line-height: 1.4;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
  /* a long destination gets its own row rather than being ellipsised to nothing beside the
     state chip — one line of text is a better index than two words and an ellipsis */
  flex: 1 1 62%;
}

/* state = a small tinted chip, coloured by meaning */
.state {
  font-family: var(--mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  background: var(--sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 2px 8px;
  white-space: nowrap;
  font-weight: 620;
  flex: none;
  line-height: 1.5;
}
.state[data-state="review"],
.state[data-state="running"]   { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line); }
.state[data-state="ready"],
.state[data-state="printed"],
.state[data-state="shipped"],
.state[data-state="confirmed"] { color: var(--ok); background: var(--ok-soft); border-color: var(--ok-line); }
.state[data-state="error"]     { color: var(--danger); background: var(--danger-soft); border-color: var(--danger-line); }
.state[data-state="confirmed"] { color: var(--warn); background: var(--warn-soft); border-color: var(--warn-line); }
.state[data-state="running"]   { border-style: dashed; }

/* -------------------------------------------------------------- case head */

/* Phone-only: the drill-down's way back. On the desk the list is right there in the rail, so a
   "◀ Cases" control would be a second navigation for a journey that does not exist. Hidden at the
   BASE level, because a rule that only exists inside the phone media query leaves the element
   falling back to `inline-block` on the desk. */
.case-back { display: none; }

.case-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  /* a compact strip: it sits above a step rail, and the two together were eating the top of the
     case. The id is secondary to the name, so it goes underneath at a small size rather than
     costing a whole line of its own. */
  padding: 10px var(--s5) 8px;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  position: sticky;
  top: 0;
  z-index: 3;
  flex: none;
}
.case-title { min-width: 0; }
.case-id {
  margin: 0 0 1px;
  font-size: 11px;
  letter-spacing: 0.01em;
  color: var(--ink-4);
  font-family: var(--mono);
  font-weight: 500;
}
.case-head h1 {
  margin: 0;
  font-size: 20px;
  letter-spacing: -0.025em;
  line-height: 1.2;
  font-weight: 680;
  overflow-wrap: anywhere;
}
.case-actions { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; justify-content: flex-end; flex: none; }
/* Phone-only: the foot of the case carries "Delete this case" there. On the desk the header has
   the button, so this row must not render at all. */
.danger-foot { display: none; }

.banner {
  margin: 0;
  padding: 10px var(--s5);
  font-size: 13px;
  background: var(--danger-soft);
  color: var(--danger);
  border-bottom: 1px solid var(--danger-line);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s3);
  flex: none;
}
.banner[data-kind="info"] { background: var(--accent-soft); color: var(--accent); border-bottom-color: var(--accent-line); }
.banner-close {
  font: inherit; line-height: 1; color: inherit;
  background: transparent; border: 1px solid transparent;
  border-radius: var(--r-sm); padding: 2px; cursor: pointer; opacity: .75;
  display: inline-flex; align-items: center;
}
.banner-close:hover { opacity: 1; background: rgba(0,0,0,0.06); }

/* ------------------------------------------------------- stepper (segmented) */

.stepper {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: var(--s3) var(--s5) var(--s2);
  background: var(--surface);
  flex: none;
  overflow-x: auto;
}
.step {
  font: inherit;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px 7px 11px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  color: var(--ink-3);
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
  white-space: nowrap;
}
.step:hover:not(.on) { background: var(--hover); color: var(--ink-2); }
.step-n {
  font-family: var(--mono);
  font-size: 12px;
  width: 20px; height: 20px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-3);
  border-radius: var(--r-pill);
  flex: none;
  font-weight: 620;
  color: var(--ink-4);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.step-l { font-size: 13px; font-weight: 580; }
.step.on {
  color: var(--accent);
  background: var(--accent-soft);
  border-color: var(--accent-line);
}
.step.on .step-n {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
/* completed steps are marked with a subdued check, never a loud fill — only the current
   step is highlighted (accent); future steps stay grey. */
.step.done { color: var(--ink-2); }
.step.done .step-n {
  background: var(--sunken); border-color: var(--line-3); color: var(--ink-3);
  position: relative; font-size: 0;
}
.step.done .step-n::after {
  content: ""; position: absolute; inset: 0;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'><path d='M2.4 7.6 5.5 10.7 11.6 3.4' fill='none' stroke='black' stroke-width='2.2'/></svg>") center / 11px 11px no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'><path d='M2.4 7.6 5.5 10.7 11.6 3.4' fill='none' stroke='black' stroke-width='2.2'/></svg>") center / 11px 11px no-repeat;
  background-color: var(--ink-3);
}
.step-arrow { display: inline-flex; align-items: center; color: var(--line-3); padding: 0 1px; flex: none; }
.step-arrow svg { width: 12px; height: 12px; }

.step-caption {
  margin: 0;
  padding: var(--s2) var(--s5) var(--s3);
  font-size: 13px;
  color: var(--ink-2);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  max-width: 84ch;
}

/* ------------------------------------------------------------ stage card */

.stage { padding: var(--s4) var(--s5) var(--s5); }
.step-card-head { margin-bottom: var(--s3); }
.step-card-head h3 {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 680;
  letter-spacing: -0.015em;
}
.step-card-head p { margin: 0; font-size: 13px; color: var(--ink-2); max-width: 78ch; }
.stage-btnrow { display: flex; gap: var(--s2); flex-wrap: wrap; margin: var(--s3) 0; }

.instruct-label {
  display: block;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-3);
  font-weight: 660;
  margin-bottom: 6px;
}

/* intake: context + files, one message */
#intake-context, #chat-input {
  font: inherit;
  font-family: var(--mono);
  font-size: 13px;
  width: 100%;
  padding: 10px 12px;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  resize: vertical;
  min-height: 56px;
  line-height: 1.55;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
#intake-context:focus-visible, #chat-input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--ring);
}
.intake-actions { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin: var(--s3) 0; }
.intake-picked { font-size: 12px; color: var(--ink-3); }
.instruct-hint { margin: 6px 0 var(--s3); font-size: 12px; color: var(--ink-3); }

.filelist {
  list-style: none; margin: 0; padding: 0;
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--raised); overflow: hidden;
}
.filerow {
  display: grid;
  /* kind | file name | what the operator calls the document | remove */
  grid-template-columns: 62px minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: var(--s3);
  align-items: center;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
}
.filerow:last-child { border-bottom: 0; }
.filerow.empty-row {
  display: block; color: var(--ink-3); font-size: 13px;
  border-bottom: 0; padding: var(--s3);
}
.fkind {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-3); font-weight: 640;
}
.fname { font-size: 13px; overflow-wrap: anywhere; color: var(--ink); }
.fdel {
  font: inherit; font-family: var(--ui); font-size: 12px; font-weight: 560;
  color: var(--danger); background: transparent;
  border: 1px solid transparent; border-radius: var(--r-sm);
  padding: 3px 8px; cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.fdel:hover { background: var(--danger-soft); border-color: var(--danger-line); }

/* --------------------------------------------------------- the field sheet */

.group {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--raised);
  overflow: hidden;
  margin-bottom: var(--s4);
  box-shadow: var(--e1);
}
.group > h3 {
  margin: 0;
  padding: 10px var(--s4);
  font-size: 12px;
  font-weight: 680;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-3);
  background: var(--sunken);
  border-bottom: 1px solid var(--line);
}
.row {
  display: grid;
  grid-template-columns: 200px minmax(190px, 1fr);
  gap: var(--s4);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line);
}
.row:last-child { border-bottom: 0; }
.row-label { font-size: 13px; font-weight: 600; color: var(--ink); line-height: 1.4; }
.row-label .page {
  display: block; font-family: var(--mono); font-size: 12px; color: var(--ink-4);
  font-weight: 400; margin-top: 3px;
}
.value { display: block; width: 100%; min-width: 0; }
.value input, .value textarea {
  font: inherit;
  font-family: var(--mono);
  font-size: 13px;
  width: 100%;
  padding: 8px 10px;
  color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  resize: vertical;
  line-height: 1.55;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), background var(--dur) var(--ease);
}
.value textarea { min-height: 64px; }
.value input:hover:not([readonly]), .value textarea:hover:not([readonly]) { border-color: var(--line-3); }
.value input:focus-visible, .value textarea:focus-visible {
  outline: none; border-color: var(--accent); box-shadow: var(--ring);
}
.value input[readonly], .value textarea[readonly] {
  background: var(--sunken); color: var(--ink-2); border-color: var(--line);
}

/* confidence: a small tinted chip; medium carries a subtle hatch */
.provenance {
  display: flex; gap: var(--s2); align-items: center;
  flex-wrap: wrap; min-width: 0; margin-top: 8px;
}
.conf-pill {
  font: inherit;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 660;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-2);
  background: var(--sunken);
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  padding: 3px 10px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.conf-pill:hover { border-color: var(--line-3); box-shadow: var(--e1); }
.conf-pill.lookup { cursor: default; color: var(--ink-4); background: transparent; border-style: dashed; }
.conf-pill.lookup:hover { box-shadow: none; border-color: var(--line-2); }
.conf-caret { font-size: 9px; line-height: 1; opacity: .75; }
.conf-pill[data-c="high"]   { background: var(--ok-soft); border-color: var(--ok-line); color: var(--ok); }
.conf-pill[data-c="medium"] { background: var(--warn-soft); border-color: var(--warn-line); color: var(--warn); }
.conf-pill[data-c="low"]    { background: var(--danger-soft); border-color: var(--danger-line); color: var(--danger); }

.reason {
  margin: 8px 0 0;
  padding: 10px 12px;
  background: var(--sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink-2);
  white-space: pre-wrap;
  word-break: break-word;
}
.provenance .src { color: var(--ink-4); font-size: 12px; font-family: var(--mono); }
.cand { margin: var(--s2) 0 0; padding: 0; list-style: none; }
.cand li { padding: 5px 0; border-bottom: 1px solid var(--line); font-size: 13px; }
.cand li:last-child { border-bottom: 0; }
.notes {
  margin: 0; padding: var(--s3) var(--s4) var(--s4);
  list-style: none; font-size: 13px; color: var(--ink-2);
}
.notes li { padding: 5px 0 5px 18px; position: relative; overflow-wrap: anywhere; }
.notes li::before {
  content: ""; position: absolute; left: 0; top: 13px;
  width: 6px; height: 4px; border-left: 1.5px solid var(--ink-4);
  border-bottom: 1.5px solid var(--ink-4);
  transform: rotate(-45deg);
}

/* --------------------------------------------------------- print assembly */

.assembly {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--raised);
  margin-top: var(--s4);
  overflow: hidden;
  box-shadow: var(--e1);
}
.assembly h2 {
  margin: 0; padding: 10px var(--s4);
  font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.1em; color: var(--ink-3); font-weight: 680;
  background: var(--sunken); border-bottom: 1px solid var(--line);
}
.manifest { margin: 0; padding: var(--s3) var(--s4); font-size: 12px; white-space: pre-wrap; color: var(--ink-2); }

/* ------------------------------------------------------- courier / waybill */

.verify-waybill {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--raised);
  margin-bottom: var(--s4);
  overflow: hidden;
  box-shadow: var(--e1);
}
.verify-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  padding: 10px var(--s4); border-bottom: 1px solid var(--line);
  background: var(--sunken);
}
.verify-head .instruct-label { margin-bottom: 0; }
.waybill-actions { display: inline-flex; gap: var(--s1); }
.waybill {
  margin: 0; padding: var(--s3) var(--s4);
  max-height: 320px; overflow: auto;
  font-size: 12px; line-height: 1.65; white-space: pre-wrap; color: var(--ink);
  background: var(--raised);
}
.apply {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s3) var(--s4); border-top: 1px solid var(--line);
  font-size: 13px; color: var(--ink-2); background: var(--sunken);
}
.ship-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s3); margin-top: var(--s3); }
.cf { display: block; min-width: 0; }
.cf.wide { grid-column: 1 / -1; }
.cf > span {
  display: block; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); margin-bottom: 5px; font-weight: 640;
}
.cf input, .cf textarea {
  font: inherit; font-family: var(--mono); font-size: 13px; width: 100%;
  padding: 8px 10px; color: var(--ink); background: var(--raised);
  border: 1px solid var(--line-2); border-radius: var(--r-md); resize: vertical;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.cf input:focus-visible, .cf textarea:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.courier-actions { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s4); }
.courier-state { font-size: 12px; color: var(--ink-3); margin-left: auto; }
.courier-state[data-kind="ok"] { color: var(--ok); }
.courier-state[data-kind="notsent"] { color: var(--danger); font-weight: 640; }
.courier-note { font-size: 13px; color: var(--danger); margin: var(--s2) 0 0; }

/* ---------------------------------------------------------- activity feed */

/* `hidden` must win over the flex sizing below, or the feed leaves an empty band at the foot of
   the steps that do not carry it. `[hidden]` is only `display: none` at UA priority. */
.chat-wrap[hidden] { display: none !important; }

.chat-wrap {
  border-top: 1px solid var(--line);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  margin-top: auto;
  flex: none;
}
body.chat-expanded .queue { display: none; }
/* the columns live on `.desk-body`, not `.desk` (which is a row grid) — targeting `.desk` here
   did nothing, so the feed stayed inside the rail's 380px track */
body.chat-expanded .desk-body { grid-template-columns: minmax(0, 1fr); }
body.chat-expanded .desk { grid-template-rows: auto minmax(0, 1fr); }
body.chat-expanded .sheet { min-width: 0; }
body.chat-expanded .sheet-scroll { overflow: hidden; }
body.chat-expanded .stage { display: none; }
body.chat-expanded #sheet-scroll { justify-content: flex-start; }
/* in expanded view the chat-wrap must take the full sheet height and let the feed scroll
   inside it — otherwise it grows to content height and the overflow:hidden clips it, and
   the whole panel is unscrollable (the "expanded chat is buggy / won't scroll" bug). */
body.chat-expanded .chat-wrap { flex: 1 1 auto; min-height: 0; }
body.chat-expanded .chat { min-height: 0; }

body.chat-running .stage { padding-bottom: 0; }
body.chat-running .running-head { padding-bottom: var(--s2); }
/* during a reading the chat becomes the scroller: the feed scrolls inside it and the
   composer stays pinned at the bottom — scrolling moves the agent's output, never the
   prompt row.
   THE HEIGHT MUST BE REAL. `flex: 1` here set flex-BASIS 0%, and a basis of 0% beats the
   `height` declaration in a flex column — the feed never got its 55vh; it got whatever was
   LEFT after the stage card, which is how a correction sent from the review card squeezed the
   feed to a 92px strip (measured) and the whole page "compressed down weirdly". `flex: none`
   + an explicit height gives the feed a stable, generous panel regardless of the card above
   it; the page scrolls to reach the rest. */
body.chat-running .chat-wrap { flex: none; height: calc(55dvh / var(--zoom, 1.1)); min-height: 300px; }
body.chat-running .chat { max-height: none; min-height: 0; flex: 1 1 auto; }

.chat-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  padding: 10px var(--s5);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}
.chat-head h2 {
  margin: 0; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--ink-3); font-weight: 660;
}
.chat-note { font-size: 12px; color: var(--ink-3); margin-left: auto; }
.chat {
  padding: var(--s3) var(--s5);
  max-height: 300px;
  overflow: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  flex: 1;
}
/* A flex column shrinks its children when the content is taller than the box. These entries
   are cards with `overflow: hidden`, so shrinking flattened each one to a 2px sliver and the
   feed read as a series of stray horizontal rules cutting across the conversation. The feed
   scrolls; its rows must keep their natural height. */
.chat > * { flex: 0 0 auto; }
body.chat-expanded .chat { max-height: none; flex: 1; }
body.chat-expanded .chat-wrap { flex: 1 1 auto; min-height: 0; }
/* Expanded on a viewport-height shell: the feed takes what is left between the feed's own head
   and the composer, and scrolls inside itself. It must NOT keep a floor height — `min-height:
   60vh` is 540px on a 900px screen, which beats the flex shrink, so the feed grew past the shell
   and pushed the composer (and its Send) 199px below the fold. The phone block sets
   `min-height: 0` for this reason; the desktop path needed it too. */
body.case-open.chat-expanded .chat { min-height: 0; overflow-y: auto; }
body.case-open.chat-expanded .chat-compose { flex: none; }
.chat-empty { color: var(--ink-3); font-size: 13px; }

/* one agent activity entry */
.act {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--raised);
  overflow: hidden;
  box-shadow: var(--e1);
}
.act + .act { margin-top: 0; }
.act-head {
  display: flex; align-items: center; gap: var(--s2);
  padding: 7px 10px;
  font-size: 12px; color: var(--ink-2);
  background: var(--sunken);
}
.act-icon { font-size: 12px; line-height: 1; flex: none; opacity: .8; }
.act-k {
  font-family: var(--mono); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--ink-3); font-weight: 680; flex: none;
  background: var(--raised); border: 1px solid var(--line-2);
  border-radius: var(--r-sm); padding: 1px 6px;
}
.act-title { font-weight: 560; min-width: 0; overflow-wrap: anywhere; font-size: 12px; }
.act-meta { margin-left: auto; font-family: var(--mono); font-size: 12px; color: var(--ink-4); white-space: nowrap; flex: none; }
.act-toggle {
  flex: none;
  font: inherit; font-family: var(--ui);
  font-size: 12px; font-weight: 560;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  padding: 2px 8px;
  cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.act-toggle:hover { background: var(--accent-soft); border-color: var(--accent-line); }
.act-body {
  padding: 10px 12px;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 340px;
  overflow: auto;
  background: var(--raised);
  border-top: 1px solid var(--line);
}
.act.collapsed .act-body { display: none; }

.chat-msg.agent {
  font-size: 13px; color: var(--ink); line-height: 1.6;
  padding: 2px 0; white-space: pre-wrap; word-break: break-word;
}
.chat-msg.user {
  align-self: flex-end;
  background: var(--accent);
  color: #fff;
  font-family: var(--ui);
  font-size: 13px;
  line-height: 1.5;
  padding: 9px 13px;
  border-radius: var(--r-lg) var(--r-lg) var(--r-sm) var(--r-lg);
  max-width: 78%;
  white-space: pre-wrap;
  word-break: break-word;
  box-shadow: var(--e1);
}
.chat-msg.system {
  align-self: flex-start;
  background: transparent;
  color: var(--ink-4);
  font-size: 12px;
  font-family: var(--mono);
  letter-spacing: 0.03em;
  padding: 1px 0;
}
.chat-compose { border-top: 1px solid var(--line); padding: var(--s3) var(--s5); background: var(--surface); }
.chat-row { display: grid; grid-template-columns: 1fr auto; gap: var(--s2); align-items: end; }
.chat-row textarea {
  font: inherit; font-family: var(--mono); font-size: 13px; width: 100%;
  padding: 9px 12px; color: var(--ink); background: var(--raised);
  border: 1px solid var(--line-2); border-radius: var(--r-md);
  resize: none; overflow: hidden; min-height: 42px;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.chat-row textarea:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.chat-hint { margin: 6px 0 0; font-size: 12px; color: var(--ink-4); }
body.chat-expanded .chat { padding-bottom: var(--s5); }
/* "showing the last N of M" — a truncation the operator can see and act on, not a silent one */
.chat-log-note {
  margin: 0 0 var(--s2); padding: 6px 10px;
  font-family: var(--mono); font-size: 11px; color: var(--ink-3);
  background: var(--sunken); border: 1px dashed var(--line-2); border-radius: var(--r-md);
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
}
.chat-log-note .btn { flex: none; }

/* ------------------------------------------------------------ inline form */

.inline-form {
  margin: var(--s4) 0;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--raised);
  overflow: hidden;
  box-shadow: var(--e1);
}
.inline-form[data-open="false"] .pager,
.inline-form[data-open="false"] .inline-page { display: none; }
.inline-form-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  padding: 10px var(--s4); border-bottom: 1px solid var(--line);
  background: var(--sunken);
}
.inline-form-head h3 {
  margin: 0; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--ink-3); font-weight: 660;
}
.inline-form-actions { display: inline-flex; gap: var(--s1); }
.inline-page { display: block; max-width: 100%; margin: 0 auto; background: #fff; width: auto; height: auto; }

/* ------------------------------------------------------------ form overlay */

.form-overlay {
  position: fixed; inset: 0;
  background: rgba(20, 23, 27, 0.55);
  display: flex; flex-direction: column;
  z-index: 40;
  padding: var(--s5);
  overscroll-behavior: contain;
}
.form-overlay-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  padding: var(--s4);              /* was --s3: the title row sat 12px from the panel's own border */
  flex: none;
}
/* THE TITLE ROW MUST NOT WRAP ITS COUNTS.
   Seen in a dark-mode capture: `.fo-title` was a baseline flex row whose summary could shrink below
   its content, so "71 notifications · 71 unseen · 1 cleared" broke and left "· 1 cleared" orphaned
   on a second line while the heading stayed on the first. The heading holds its size; the summary
   takes the rest and keeps each count whole. */
/* THE SHARED RULE STAYS SHARED — this is the BASE for every overlay's title row.
   An earlier edit REPLACED this with a `#notify-overlay`-scoped version, which silently removed
   `display:flex` from every OTHER overlay's title row and changed the My Servers header from 63px to
   75px. `verify_my_servers_header.py` caught it, because it asserts the desk header height as well
   as its own card's fix. Fix a shared component by ADDING a scoped override, never by replacing the
   base. */
.fo-title { display: flex; align-items: baseline; gap: var(--s3); }
/* ONLY THE NOTIFICATIONS PANEL IS OVERRIDDEN. Its summary holds three counts; without this the flex
   row shrank it below its content and "1 cleared" wrapped onto a second line. The My Servers overlay
   has its own scoped rules for the same row (and `verify_my_servers_header.py` proves they do not
   leak), so nothing generic is changed here — a shared base rule must not be tightened for one
   caller, or every other caller's geometry moves with it. */
#notify-overlay .fo-title { min-width: 0; }
#notify-overlay .fo-title h2 { flex: none; }
#notify-overlay .fo-title .mono { flex: 0 1 auto; min-width: 0; }
.fo-title h2 { margin: 0; font-size: 15px; font-weight: 680; }
.fo-title .mono { font-size: 12px; color: var(--ink-4); }
.fo-actions { display: flex; gap: var(--s2); }
.form-overlay-body {
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: 0 0 var(--r-xl) var(--r-xl);
  overflow: auto;
  flex: 1;
  padding: var(--s4);
  overscroll-behavior: contain;
}
.pager { display: flex; align-items: center; gap: 5px; margin-bottom: var(--s3); }
.pager-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--ink-3); font-weight: 640;
}
.pg {
  font: inherit; font-family: var(--mono); font-size: 12px; font-weight: 560;
  min-width: 30px; height: 28px; padding: 0 8px;
  background: var(--raised); color: var(--ink-2);
  border: 1px solid var(--line-2); border-radius: var(--r-sm); cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.pg:hover { border-color: var(--line-3); color: var(--ink); }
.pg.on { background: var(--accent); border-color: var(--accent); color: #fff; }
.fo-page {
  display: block; max-width: 100%; margin: 0 auto;
  background: #fff; box-shadow: var(--e3); width: auto; height: auto;
}

/* ------------------------------------------------------------------ modal */

.modal-wrap {
  position: fixed; inset: 0;
  background: rgba(20, 23, 27, 0.5);
  display: flex; align-items: center; justify-content: center;
  padding: var(--s4);
  z-index: 60;
  overscroll-behavior: contain;
}
.modal {
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xl);
  max-width: 520px; width: 100%;
  padding: var(--s5);
  box-shadow: var(--e3);
}
.modal h3 { margin: 0 0 var(--s3); font-size: 17px; font-weight: 680; letter-spacing: -0.02em; }
.modal-body {
  margin: 0 0 var(--s4); padding: var(--s3);
  background: var(--sunken); border: 1px solid var(--line);
  border-radius: var(--r-md);
  font-size: 13px; white-space: pre-wrap; color: var(--ink-2);
  line-height: 1.6;
}
.modal-type { margin-bottom: var(--s4); }
.modal-type label {
  display: block; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); margin-bottom: 6px; font-weight: 650;
}
.modal-type input {
  font: inherit; font-family: var(--mono); font-size: 13px; width: 100%;
  padding: 9px 11px; border: 1px solid var(--line-2); border-radius: var(--r-md);
  background: var(--raised); color: var(--ink);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.modal-type input:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.modal-actions { display: flex; justify-content: flex-end; gap: var(--s2); }

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

.empty { padding: var(--s6) var(--s5); max-width: 56ch; }
.empty-line { margin: 0 0 var(--s2); font-size: 18px; font-weight: 680; letter-spacing: -0.02em; }
.empty-sub { margin: 0; color: var(--ink-2); font-size: 14px; }

/* ------------------------------------------------------- the workflow tabs
   Three workflows across the top. Hague is the left default. Each tab owns its own cases,
   its own search and its own create action. */

.wfbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); flex-wrap: wrap;
  padding: var(--s3) var(--s4);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}
.wf-tabs { display: flex; gap: 2px; padding: 3px; background: var(--sunken);
  border: 1px solid var(--line); border-radius: var(--r-md); }
.wf-tab {
  font: inherit; font-size: 13px; font-weight: 640;
  padding: 7px 18px; border-radius: var(--r-sm);
  color: var(--ink-2); background: transparent; border: 1px solid transparent;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.wf-tab:hover { background: var(--hover); color: var(--ink); }
.wf-tab.on {
  color: var(--accent); background: var(--raised);
  border-color: var(--line-2); box-shadow: var(--e1);
}
.wf-tab .wf-n {
  font-family: var(--mono); font-size: 11px; color: var(--ink-4);
  margin-left: 7px; font-weight: 600;
}
.wf-tab.on .wf-n { color: var(--accent); }
/* THE GREYED TAB — THE BLANKET'S ONE VISIBLE MARK (the operator's 2026-09-24 word: a
   blanket-blocked workflow is greyed out for the people it is blocked for, everyone else sees it
   normal). Greyness is the WHOLE statement, not a disabled control: the tab stays clickable (the
   work that exists is still readable) and the routes behind its acts still refuse (R14). The
   reduced contrast, not a cursor change and not `disabled`, because the reason travels on the
   tab's own title tooltip. */
.wf-tab.wf-blocked {
  color: var(--ink-4); background: transparent; border-color: transparent;
  cursor: pointer; opacity: .55;
}
.wf-tab.wf-blocked:hover { background: var(--hover); color: var(--ink-3); opacity: .75; }
.wf-tab.wf-blocked .wf-n { color: var(--ink-4); }

.wf-tools { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
/* Phone-only controls. Hidden here and switched on inside the phone media query, so nothing
   phone-shaped can ever render on the desk. */
.wf-menu, .wf-search-toggle, .wf-sheet-wrap { display: none; }
.search-wrap { position: relative; display: flex; align-items: center; }
.search {
  font: inherit; font-family: var(--mono); font-size: 13px;
  width: 30ch; min-width: 18ch; padding: 8px 30px 8px 12px;
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-md);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.search::placeholder { color: var(--ink-4); font-size: 12px; }
.search:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.search-clear {
  position: absolute; right: 4px; width: 22px; height: 22px;
  font: inherit; font-size: 12px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-3); background: transparent; border: 0; border-radius: var(--r-pill);
  cursor: pointer;
}
.search-clear:hover { background: var(--hover); color: var(--ink); }

/* the desk body under the tab bar: rail + sheet */
.desk-body {
  display: grid; grid-template-columns: var(--rail) 1fr;
  min-height: 0; overflow: hidden;
}

/* the rail carries whatever the selected tab shows */
.rail-body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--s2); }
.rail-count { font-size: 12px; color: var(--ink-4); }
.rail-note { padding: var(--s3) var(--s2); }
.rail-note .empty-line { font-size: 15px; }
.rail-note .empty-sub { font-size: 13px; }

/* --------------------------------------------------------- new-case modal */

.modal-wide { max-width: 760px; }
.nc-sub { margin: 0 0 var(--s4); color: var(--ink-2); font-size: 13px; }
.nc-hint { margin: 6px 0 var(--s3); color: var(--ink-3); font-size: 12px; line-height: 1.55; }
.nc-hint em { font-style: normal; color: var(--ink-2); }
.nc-docs {
  margin: var(--s4) 0;
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--sunken); padding: var(--s3) var(--s4);
}
.nc-docs-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }
.nc-actions { display: inline-flex; align-items: center; gap: var(--s2); }
.nc-count { font-size: 12px; color: var(--ink-4); }
.nc-list { list-style: none; margin: var(--s2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--s2); }
.nc-row {
  display: grid; grid-template-columns: minmax(0, 15ch) minmax(0, 1fr) auto;
  gap: var(--s2); align-items: center;
  background: var(--raised); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: var(--s2) var(--s3);
}
.nc-file { font-family: var(--mono); font-size: 12px; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nc-label {
  font: inherit; font-size: 13px; width: 100%; padding: 7px 10px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.nc-label:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.nc-remove {
  font: inherit; font-size: 12px; color: var(--ink-3);
  background: transparent; border: 0; cursor: pointer; padding: 4px 6px;
  border-radius: var(--r-sm);
}
.nc-remove:hover { color: var(--danger); background: var(--danger-soft); }
.nc-drop {
  margin-top: var(--s3); padding: var(--s4);
  border: 1px dashed var(--line-3); border-radius: var(--r-md);
  text-align: center; color: var(--ink-4); font-size: 12px;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.nc-drop.hot { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.nc-empty { color: var(--ink-3); font-size: 13px; margin: var(--s2) 0 0; }

/* the selected tab's own header inside the sheet */
.wf-intro { margin-bottom: var(--s5); max-width: 68ch; }
.wf-intro h2 {
  margin: 0 0 6px; font-size: 20px; font-weight: 680; letter-spacing: -0.02em;
}
.wf-intro p { margin: 0; color: var(--ink-2); font-size: 13px; line-height: 1.6; }

/* a document's operator-given name, on the intake file list */
.filerow .flabel {
  font-size: 12px; color: var(--accent); font-family: var(--mono);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.filerow .flabel.missing { color: var(--warn); }
.filerow .flabel-edit {
  font: inherit; font-size: 12px; font-family: var(--mono);
  width: 100%; padding: 4px 7px;
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
}
.filerow .flabel-edit:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }

/* the printer picker on the print step, and the office/printer pair it shows */
.printer-pick { display: flex; flex-direction: column; gap: var(--s2); margin: var(--s3) 0; }
.printer-row {
  display: flex; align-items: center; gap: var(--s3);
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--raised); padding: var(--s3) var(--s4);
  cursor: pointer;
}
.printer-row:hover { border-color: var(--line-3); }
.printer-row.on { border-color: var(--accent-line); background: var(--accent-soft); }
.printer-office { font-weight: 640; font-size: 13px; }
.printer-name { font-family: var(--mono); font-size: 12px; color: var(--ink-3); margin-left: auto; }
.printer-addr { font-family: var(--mono); font-size: 11px; color: var(--ink-4); }

/* ------------------------------------------------------- the print set
   What will print, as a thing the operator can hold and reorder. The form is pinned at the
   head of each copy and cannot be dragged; everything else is. */

.pset { margin: var(--s4) 0; }
.pset-head {
  display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap;
  margin-bottom: var(--s2);
}
.pset-head h2 {
  margin: 0; font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.1em; color: var(--ink-3); font-weight: 680;
}
.pset-counts { font-family: var(--mono); font-size: 12px; color: var(--ink-3); margin-left: auto; }
.pset-copy { margin-bottom: var(--s4); }
.pset-copy-head {
  display: flex; align-items: center; gap: var(--s3);
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-4); font-weight: 650;
  padding: 0 2px var(--s1);
}
.pset-copy-head .pset-copy-n {
  color: var(--ink-2); background: var(--sunken);
  border: 1px solid var(--line); border-radius: var(--r-sm); padding: 1px 7px;
}
.pset-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.pset-item {
  display: grid; grid-template-columns: 22px 26px minmax(0, 1fr) auto 24px;
  align-items: center; gap: var(--s2);
  background: var(--raised); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 8px 12px;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.pset-item.pinned { background: var(--accent-soft); border-color: var(--accent-line); }
.pset-item[draggable="true"] { cursor: grab; }
.pset-item.dragging { opacity: 0.45; }
.pset-item.drop-before { border-top: 2px solid var(--accent); }
.pset-item.drop-after { border-bottom: 2px solid var(--accent); }
.pset-grip {
  font-size: 13px; color: var(--ink-4); text-align: center; line-height: 1;
  user-select: none;
}
.pset-item.pinned .pset-grip { color: var(--accent); opacity: 0.6; }
.pset-n {
  font-family: var(--mono); font-size: 12px; color: var(--ink-4);
  text-align: right; font-feature-settings: "tnum" 1;
}
.pset-body { min-width: 0; }
/* both lines must be block-level: as inline spans the title and its metadata run together on
   one line ("Klageschrift (DE)doc1.pdf · not yet read…"), and text-overflow needs a block
   container to ellipsise at all */
.pset-title {
  display: block;
  font-size: 13px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pset-title.unnamed { color: var(--warn); font-weight: 620; }
.pset-sub {
  display: block;
  font-family: var(--mono); font-size: 11px; color: var(--ink-4);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pset-tags { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.pset-tag {
  font-family: var(--mono); font-size: 11px; font-weight: 620;
  border-radius: var(--r-pill); padding: 1px 8px; white-space: nowrap;
}
.pset-tag.tr { color: var(--accent); background: var(--accent-soft); border: 1px solid var(--accent-line); }
.pset-tag.bundle { color: var(--ok); background: var(--ok-soft); border: 1px solid var(--ok-line); }
.pset-tag.machine { color: var(--warn); background: var(--warn-soft); border: 1px solid var(--warn-line); }
.pset-tag.unnamed { color: var(--warn); background: var(--warn-soft); border: 1px solid var(--warn-line); }
.pset-tag.meta { color: var(--ink-3); background: var(--sunken); border: 1px solid var(--line); }
.pset-actions { display: flex; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s2); }
/* the remove control on each row, and the list of documents taken out of the set */
.pset-x {
  font: inherit; font-size: 12px; line-height: 1; color: var(--ink-4);
  background: transparent; border: 0; cursor: pointer;
  width: 22px; height: 22px; border-radius: var(--r-sm);
  display: flex; align-items: center; justify-content: center;
}
.pset-x:hover { color: var(--danger); background: var(--danger-soft); }
.pset-excluded {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  margin-top: var(--s3); padding: var(--s3) var(--s4);
  border: 1px dashed var(--line-3); border-radius: var(--r-md);
  background: var(--sunken);
}
.pset-excluded-head {
  font-size: 12px; color: var(--ink-3); font-weight: 620;
}
.pset-warn {
  margin: var(--s2) 0 0; padding: var(--s3) var(--s4);
  background: var(--warn-soft); border: 1px solid var(--warn-line);
  border-radius: var(--r-md); font-size: 12px; color: var(--ink-2); line-height: 1.6;
}
.pset-warn b { color: var(--warn); }
.pset-note { font-size: 12px; color: var(--ink-3); line-height: 1.6; margin: var(--s2) 0 0; }

/* the translate decision, on the review step */
.tr-box {
  margin: var(--s3) 0; padding: var(--s3) var(--s4);
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--raised);
}
.tr-box h4 { margin: 0 0 6px; font-size: 13px; font-weight: 660; }
.tr-row { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s2); }
.tr-lang {
  font: inherit; font-family: var(--mono); font-size: 13px;
  padding: 7px 10px; min-width: 16ch;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
}
.tr-lang:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.tr-state { font-family: var(--mono); font-size: 12px; color: var(--accent); }
.tr-state.off { color: var(--ink-3); }
.tr-state.busy { color: var(--warn, var(--accent)); }

/* ------------------------------------------------------------- STEP 3 · TRANSLATION
   The translation step is its own screen: the receiving authority's requirement with its HCCH
   source, the documents the requirement applies to, the translated set with each document's
   reviewed flag, and the controls the pipeline module offered. Blocks are cards on the raised
   surface, the same stack the rest of the console uses. */
.tr-block {
  margin: var(--s4) 0 0; padding: var(--s3) var(--s4);
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--raised);
}
.tr-block h4 {
  margin: 0 0 var(--s2); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.1em; color: var(--ink-3); font-weight: 680;
  display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap;
}
.tr-count { font-family: var(--mono); font-size: 11px; letter-spacing: 0; text-transform: none; color: var(--ink-3); }
.tr-count::before { content: "·"; margin-right: var(--s2); color: var(--line-3); }
.tr-req-line { margin: 0 0 var(--s2); font-size: 13px; line-height: 1.65; color: var(--ink); white-space: pre-wrap; }
.tr-absent { margin: var(--s2) 0 0; font-size: 13px; line-height: 1.6; color: var(--ink-3); }
.tr-src { margin: var(--s2) 0 0; display: flex; gap: var(--s2); flex-wrap: wrap; align-items: baseline; }
.tr-src a { color: var(--accent); font-family: var(--mono); font-size: 12px; overflow-wrap: anywhere; }
.tr-src .mono { font-size: 12px; color: var(--ink-3); }
.tr-note { margin: var(--s2) 0 0; font-size: 12px; line-height: 1.6; color: var(--ink-3); }
.tr-note.ok { color: var(--ok, var(--accent)); }
.tr-flag {
  margin: var(--s2) 0 0; padding: 7px var(--s3); font-size: 12.5px; line-height: 1.6;
  color: var(--ink); background: var(--warn-bg, var(--surface));
  border-left: 2px solid var(--warn, var(--accent)); border-radius: var(--r-sm);
}
.tr-docs { list-style: none; margin: var(--s2) 0 0; padding: 0; display: grid; gap: 6px; }
.tr-doc, .tr-item {
  display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap;
  padding: 6px 0; border-top: 1px solid var(--line);
}
.tr-doc:first-child, .tr-item:first-child { border-top: 0; }
.tr-doc-title { font-size: 13px; color: var(--ink); font-weight: 560; overflow-wrap: anywhere; }
.tr-doc-file { font-size: 12px; color: var(--ink-3); overflow-wrap: anywhere; }
.tr-doc-lang, .tr-doc-pages { font-size: 12px; color: var(--ink-3); }
.tr-tag {
  font-family: var(--mono); font-size: 11px; padding: 2px 7px; border-radius: 999px;
  border: 1px solid var(--line-2); color: var(--ink-2); white-space: nowrap;
}
.tr-tag.ok { color: var(--ok, var(--accent)); border-color: currentColor; }
.tr-tag.quiet { color: var(--ink-3); }
.tr-controls { margin-top: var(--s3); }
.tr-settled { margin: 0 0 var(--s2); font-size: 13px; line-height: 1.6; }
.tr-settled.ok { color: var(--ok, var(--accent)); }
.tr-settled.open { color: var(--warn, var(--accent)); }
.tr-refusals { list-style: none; margin: var(--s3) 0 0; padding: 0; display: grid; gap: 4px; }
.tr-refusal { font-size: 12px; line-height: 1.6; color: var(--ink-3); }
.tr-refusal .mono { font-family: var(--mono); color: var(--ink-2); }
.tr-lang-wrap { display: inline-flex; }

* { scrollbar-width: thin; scrollbar-color: var(--line-3) transparent; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--line-3); border: 3px solid transparent;
  background-clip: content-box; border-radius: var(--r-pill);
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-4); background-clip: content-box; }

/* one authored moment: a state chip settles when a case's status changes */
@keyframes chip-in {
  from { opacity: 0; transform: translateY(-2px); }
  to   { opacity: 1; transform: none; }
}
.state.landed { animation: chip-in 200ms var(--ease); }

/* the mark's ring turns slowly, the console's only ambient motion */
@keyframes rose-turn { to { transform: rotate(360deg); } }
.mark-rose .rosette-weave { transform-origin: 20px 20px; animation: rose-turn 60s linear infinite; }

@media (prefers-reduced-motion: reduce) {
  .state.landed { animation: none; }
  .btn:active:not(:disabled) { transform: none; }
  .mark-rose .rosette-weave { animation: none; }
  * { transition-duration: 1ms !important; }
}

/* -------------------------------------------------------------- responsive */

@media (max-width: 1120px) {
  :root { --rail: 320px; }
  .row { grid-template-columns: 170px minmax(0, 1fr); }
}
@media (max-width: 980px) {
  .desk-body { grid-template-columns: 1fr; }
  .sheet { border-radius: 0; border-left: 0; }
  .queue { border-right: 0; border-bottom: 1px solid var(--line); }
  .rail-body { max-height: 232px; overflow: auto; }
  .wfbar { padding: var(--s3) var(--s4); }
  .search { width: 100%; min-width: 0; }
  .search-wrap { flex: 1 1 24ch; }
  .bar { height: auto; padding: var(--s3) var(--s4); flex-wrap: wrap; gap: var(--s3); }
  .bar-meta { flex-wrap: wrap; gap: var(--s3); }
  .case-head { padding: var(--s4) var(--s4) var(--s3); }
  .stage { padding: var(--s3) var(--s4) var(--s4); }
  .step-caption, .chat-head, .chat-compose { padding-left: var(--s4); padding-right: var(--s4); }
  .chat { padding-left: var(--s4); padding-right: var(--s4); }
  .stepper { padding-left: var(--s4); padding-right: var(--s4); }
}
@media (max-width: 860px) {
  .row { grid-template-columns: 1fr; gap: var(--s2); }
  .provenance { display: flex; }
  .ship-grid, .courier-grid { grid-template-columns: 1fr; }
  .step-l { display: none; }
  .step { padding: 7px 10px; }
  .step-arrow { display: none; }
  .form-overlay { padding: var(--s2); }
  /* the title needs the full width before the actions claim theirs, or it wraps per character */
  .case-head { flex-wrap: wrap; align-items: flex-start; gap: var(--s3); }
  .case-title { flex: 1 1 100%; min-width: 0; }
  .case-actions { width: 100%; justify-content: flex-start; }
}

/* ------------------------------------------------------------- candidate ladder */
/* A contradictory field shows the chosen value plus every other reading, ranked by */

/* confidence high→low. Each option carries its own source, opened in the highlight view. */
.cand-ladder { margin: 8px 0 0; border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; }
.cand-ladder > summary {
  cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 8px;
  padding: 6px 12px; font-size: 12px; font-family: var(--mono);
  color: var(--warn); background: var(--warn-soft);
  border-radius: var(--r-md);
}
.cand-ladder > summary .caret { margin-left: auto; font-size: 10px; }
.cand-ladder[open] > summary { border-radius: var(--r-md) var(--r-md) 0 0; }
.cand-list { list-style: none; margin: 0; padding: 0; background: var(--raised); }
.cand-item {
  padding: 10px 12px; border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 6px; font-size: 13px;
}
.cand-item:first-child { border-top: 0; }
.cand-item .cand-val { color: var(--ink); white-space: pre-wrap; word-break: break-word; font-family: var(--mono); }
.cand-item .cand-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cand-item .cand-ev { font-size: 12px; color: var(--ink-2); font-family: var(--mono); white-space: pre-wrap; word-break: break-word; }
.cand-src {
  margin-left: auto; flex: none;
  font: inherit; font-family: var(--ui); font-size: 12px; font-weight: 560;
  color: var(--accent); background: transparent; border: 1px solid var(--line-2);
  border-radius: var(--r-sm); padding: 2px 9px; cursor: pointer;
}
.cand-src:hover { background: var(--accent-soft); border-color: var(--accent-line); }
.cand-use {
  flex: none;
  font: inherit; font-family: var(--ui); font-size: 12px; font-weight: 620;
  color: #fff; background: var(--accent); border: 1px solid var(--accent);
  border-radius: var(--r-sm); padding: 3px 10px; cursor: pointer;
}
.cand-use:hover { filter: brightness(1.06); }

/* ----------------------------------------------------------- source highlight view */
.src-overlay {
  position: fixed; inset: 0; z-index: 60;
  background: var(--bg); padding: var(--s4);
  display: flex; flex-direction: column; gap: var(--s3);
}
.src-overlay-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
}
.src-overlay-head h2 { margin: 0; font-size: 16px; color: var(--ink); }
.so-title { display: flex; align-items: baseline; gap: var(--s3); min-width: 0; }
.so-title .mono { font-size: 12px; color: var(--ink-4); }
.src-overlay-body {
  flex: 1; overflow: auto; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  display: flex; justify-content: center; align-items: flex-start; padding: var(--s4);
}
.src-page-wrap { display: flex; flex-direction: column; align-items: center; gap: var(--s2);
  max-width: 100%; }
/* A source that opened on a page with nothing highlighted says so HERE. A banner rather than a
   caption: the note states that the view could not do its job, which is a different thing from
   the file name and page above it. */
.src-note {
  margin: 0; font-family: var(--ui); font-size: 13px; line-height: 1.45;
  color: var(--warn); background: var(--warn-soft);
  border: 1px solid var(--warn-line); border-radius: var(--r-sm);
  padding: 8px 12px; max-width: 60ch; text-align: left;
}
.src-note[hidden] { display: none !important; }
.src-page { max-width: 100%; box-shadow: var(--e1); border-radius: 2px; }

/* ------------------------------------------------------- case-name rename / trash */
.case-name-wrap { display: flex; align-items: center; gap: var(--s2); min-width: 0; }
.case-name-wrap h1 { margin: 0; overflow-wrap: anywhere; }
.btn-icon {
  flex: none; width: 28px; height: 28px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font: inherit; color: var(--ink-3);
  background: transparent; border: 1px solid transparent; border-radius: var(--r-sm);
  cursor: pointer; transition: color var(--dur) var(--ease), background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.btn-icon:hover { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line); }
.btn-icon.danger:hover { color: var(--danger); background: var(--danger-soft); border-color: var(--danger-line); }
.btn-icon .glyph { width: 15px; height: 15px; }

.entry-row { position: relative; }
.entry-row .entry { width: 100%; text-align: left; }
.entry-actions {
  position: absolute; top: 8px; right: 6px;
  display: flex; gap: 2px; opacity: 0; transition: opacity var(--dur) var(--ease);
}
.entry-row:hover .entry-actions, .entry-row:focus-within .entry-actions { opacity: 1; }
.entry-row .entry-actions .btn-icon { width: 24px; height: 24px; background: var(--surface); border-color: var(--line); }
.entry-row .entry-actions .btn-icon:hover { background: var(--accent-soft); }
.entry-row .entry-actions .btn-icon.danger:hover { background: var(--danger-soft); }
.need-dest { font-style: normal; color: var(--warn); font-weight: 620; }

/* -------------------------------------- agent activity: message boxes + timestamps */
.chat-msg { display: flex; flex-direction: column; gap: 3px; max-width: 85%; }
.chat-msg.agent {
  align-self: flex-start;
  background: var(--raised); border: 1px solid var(--line);
  border-radius: var(--r-lg) var(--r-lg) var(--r-sm) var(--r-lg);
  color: var(--ink); font-size: 13px; line-height: 1.6;
  padding: 9px 13px; box-shadow: var(--e1);
  white-space: pre-wrap; word-break: break-word;
}
.chat-msg.user { align-self: flex-end; max-width: 78%; }
.chat-msg.system { align-self: flex-start; max-width: 100%; }
.msg-meta { display: flex; align-items: center; gap: 6px; font-family: var(--mono); font-size: 11px; color: var(--ink-4); }
.chat-msg.user .msg-meta { align-self: flex-end; }
.chat-msg.agent .msg-meta { order: -1; }
.chat-msg .msg-lbl { font-weight: 680; color: var(--ink-3); text-transform: uppercase; letter-spacing: 0.06em; }
.chat-msg.user .msg-lbl { color: #fff; opacity: .9; }
.chat-msg.system .msg-meta { align-self: flex-start; }

/* translations / requirements panel */
.req-panel { margin: var(--s4) 0 0; }
.req-panel h3 { margin: 0 0 var(--s2); font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3); font-weight: 680; }
.req-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s3); }
.req-card { border: 1px solid var(--line); border-radius: var(--r-md); background: var(--raised); padding: var(--s3) var(--s4); }
.req-card h4 { margin: 0 0 4px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--ink-3); }
.req-card p { margin: 0; font-size: 13px; color: var(--ink); white-space: pre-wrap; }
.req-card .mono { font-size: 12px; color: var(--ink-2); display: block; margin-top: 4px; }
.req-card a { color: var(--accent); font-family: var(--mono); font-size: 12px; }
/* A requirements card holds a long URL and a source line. At phone width an unbroken token set a
   min-content floor and the card measured 457px in a 390px viewport — the whole page scrolled
   sideways. `minmax(0,1fr)` releases the track; `overflow-wrap` lets the URL break. */
.req-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.req-card { min-width: 0; overflow-wrap: anywhere; }
.req-card a, .req-card .mono, .req-card p { overflow-wrap: anywhere; word-break: break-word; }
@media (max-width: 620px) { .req-grid { grid-template-columns: minmax(0, 1fr); } }

/* ---------------------------------------------------------------- home view */
/* The default page: every case grouped. A tab bar of categories (All, folders, statuses,
   countries). The case chrome (id chip, title, stepper, caption, intake/agent feed) is
   hidden on home so it never reads as "no case selected". */

body.on-home .case-chrome,
body.on-home .case-head,
body.on-home .stepper,
body.on-home .step-caption,
body.on-home .chat-wrap { display: none; }
body.on-home .sheet-scroll { overflow-y: auto; }

.home-tabs {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-bottom: var(--s4);
}
.home-tab {
  font: inherit; font-family: var(--mono); font-size: 12px; font-weight: 620;
  padding: 6px 12px; border-radius: var(--r-pill);
  color: var(--ink-2); background: var(--raised);
  border: 1px solid var(--line-2); cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.home-tab:hover { border-color: var(--line-3); color: var(--ink); }
.home-tab.on { color: #fff; background: var(--accent); border-color: var(--accent); }
.home-empty { padding: var(--s5) 0; }
.home-new-folder { margin-top: var(--s2); }
.home-section { margin-bottom: var(--s5); }
.home-section-head {
  display: flex; align-items: center; gap: var(--s2);
  margin-bottom: var(--s2);
}
/* says whether a heading is one of the operator's folders or a group computed from the
   destination — without it the two are indistinguishable and a case under "France" appears to
   contradict its own "No folder" picker */
.home-kind {
  /* 11px, not 10px: the floor. At 10px with this letter-spacing the FOLDER/YOURS tag read as
     texture, not text, and the audit measured it below the console's own legibility floor. */
  font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 1px 6px; border-radius: var(--r-pill);
  color: var(--ink-3); background: var(--sunken); border: 1px solid var(--line);
}
.home-kind[data-kind="folder"],
.home-kind[data-kind="own"] { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line); }
.home-section-head .home-count { margin-left: auto; }
.home-section-head h2 {
  margin: 0; font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.1em; color: var(--ink-3); font-weight: 680;
}
.home-count { font-size: 12px; color: var(--ink-4); }
/* A category the operator made can be renamed and deleted from its own heading. Kept faint until
   the heading is hovered or the button is focused, so a list of headings stays calm — but always
   reachable by keyboard, and never `display: none`. */
.sec-acts { display: flex; align-items: center; gap: 2px; }
.sec-acts .btn-icon { width: 24px; height: 24px; opacity: 0; transition: opacity var(--dur) var(--ease); }
.home-section-head:hover .sec-acts .btn-icon,
.sec-acts .btn-icon:focus-visible { opacity: 1; }
@media (hover: none) { .sec-acts .btn-icon { opacity: 1; } }
.home-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--s3); }
.home-card {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--raised); box-shadow: var(--e1);
  padding: var(--s3) var(--s4); display: flex; flex-direction: column; gap: var(--s2);
  position: relative;
}
.home-card-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; }
/* The desk keeps the desk's behaviour: the rename/delete pair is out of the way until the card
   is pointed at. The phone block overrides this — there is no hover on touch, so there they are
   always shown. Nothing here differs from the rail's own row actions. */
.home-card-acts { display: flex; gap: 2px; opacity: 0; transition: opacity var(--dur) var(--ease); }
.home-card:hover .home-card-acts, .home-card:focus-within .home-card-acts { opacity: 1; }
.home-card .btn-icon { width: 24px; height: 24px; }
.home-card-main {
  font: inherit; text-align: left; cursor: pointer;
  background: transparent; border: 0; padding: 0;
  display: flex; flex-direction: column; gap: 6px; min-width: 0;
}
.home-card-main .entry-name { font-weight: 620; color: var(--ink); }
.home-card-meta { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.home-card-meta .entry-sub { font-family: var(--mono); font-size: 12px; color: var(--ink-2); }
.home-cat-sel {
  font: inherit; font-family: var(--mono); font-size: 12px;
  color: var(--ink); background: var(--raised);
  border: 1px solid var(--line-2); border-radius: var(--r-sm); padding: 4px 8px;
  cursor: pointer;
}
.home-folder { border: 1px solid var(--line); border-radius: var(--r-md); background: var(--surface); margin-bottom: var(--s2); }
.home-folder summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s4);
}
.home-folder summary::-webkit-details-marker { display: none; }
.home-folder summary::before {
  content: "▸"; color: var(--ink-3); font-size: 11px; transition: transform var(--dur) var(--ease);
}
.home-folder[open] summary::before { transform: rotate(90deg); }
.folder-title { font-weight: 620; color: var(--ink); }
.folder-count { font-size: 12px; color: var(--ink-4); margin-left: auto; }
.folder-actions { display: flex; gap: 2px; }
.home-folder > div { padding: 0 var(--s4) var(--s4); }
.folder-empty { color: var(--ink-3); font-size: 13px; margin: var(--s2) 0 var(--s3); }
.home-section .folder-empty { margin-left: 0; }

/* ============================================================ phones
 *
 * A phone does not get a squeezed desk. The desk is two panes side by side — a case list and
 * the sheet — and at 390px there is no room for both, so the console becomes what a phone
 * already expects for a list of things: a **drill-down**. The list is the page; picking a case
 * slides the case in; a back control at the top of the case returns to the list.
 *
 * Three things make that real rather than cosmetic:
 *   1. `body.case-open/on-home` drives which pane exists on screen, so nothing is laid out
 *      off-screen to be scrolled into by accident.
 *   2. The workflow tabs, search and create actions live in a bar that stays put at the top of
 *      the list view — the operator's first question is "which of these needs me", and that bar
 *      is how they narrow it.
 *   3. Everything tappable is at least 44px on its short side. The desktop build is dense by
 *      design; a thumb is not a mouse.
 *
 * Notch and home bar: the shell pads with the safe-area insets, so the last row of a long case
 * is never under the home indicator and the tabs are never under the notch.
 */

@media (max-width: 720px) {

  /* The desk is a grid, and a grid sizes its columns to their content. A nowrap row of buttons
     therefore set a min-content floor wider than the phone itself (measured: 455px inside a
     390px body), and the parent's overflow:hidden hid the symptom while the content sat
     off-screen. Every level down to the scroll container is pinned to the viewport width here,
     and min-width:0 releases the automatic minimum that creates the floor. */
  .bar { padding-left: max(var(--s3), env(safe-area-inset-left));
         padding-right: max(var(--s3), env(safe-area-inset-right)); }
  /* `body` is itself a grid with an implicit auto column, so an auto track let the desk's
     min-content (413px) set the column width inside a 354px body. An auto track is allowed to
     overflow its grid container, which is why max-width on the children could not help: the
     floor was set one level up. Pinning the track with minmax(0,1fr) is the structural fix —
     nothing inside can widen the page again. */
  body { grid-template-columns: minmax(0, 1fr); }
  .desk, .desk-body, .queue, .sheet, .stage, .wfbar {
    min-width: 0; max-width: 100%;
  }
  .desk { min-height: 0; width: 100%; }
  .desk-body { width: 100%; }

  /* --- the list is the page, and the page scrolls as one --- */
  .desk-body { display: block; }
  .queue { border-right: 0; border-bottom: 0; height: auto; }
  /* on the list view the sheet does not exist at all */
  body.on-home .sheet { display: none; }
  /* inside a case the list is gone, not scrolled past */
  body:not(.on-home) .queue { display: none; }
  /* The list view is ONE scroll region. Previously the rail kept its own scrollbar, so the
     cases scrolled inside a box while the bar and the page sat still — on a phone that reads
     as a trapped pane. Here the page itself scrolls and the rail stops being a scroll
     container, so the whole list moves together under the thumb. The bar stays pinned because
     it is the list's own control (workflow + search), not page furniture. */
  /* `html`/`body` are pinned to `height: 100%` for the desktop app shell, and the desk is a grid
     whose second row is `1fr` — so the desk never grows: the rail's content was clipped at 767px
     with 442px of cases hidden below, and `body` could not scroll because it had nothing to
     scroll. On a phone the list must be as tall as its content, so the height chain is released
     from `body` down and the document becomes the scroller. */
  /* `html` carries `overflow: hidden` (the desktop app shell) and `height: 100%`, so a scroller
     on `body` becomes a 767px-tall box INSIDE a fixed 844px html: body's own scrollable height
     equals its client height and nothing moves. On a phone the document itself must scroll, so
     html is released to `overflow-y: auto` with its height free, and body stops being a scroller
     and simply grows to its content. */
  /* both phone views scroll the document: the list and the case */
  /* X is pinned shut and Y is the scroller. `overflow-y: auto` on its own leaves the X axis
     `visible`, and `zoom: 1.1` rounds the layout up a fraction of a pixel — enough for the phone
     page to be dragged a few px sideways. Pin the axis we never want scrolled. */
  html:has(body.on-home),
  html:has(body.case-open) {
    overflow-x: clip; overflow-y: auto;
    height: auto; overscroll-behavior-y: contain;
  }
  /* `position: sticky` needs a scrolling ancestor whose content is taller than itself. With the
     document now scrolling, the sticky bar's containing block (`body` -> `.desk` -> `.wfbar`)
     must not be its own overflow context, or the sticky resolves against a box that never
     scrolls and the bar rides away with the content. `overflow: visible` on the chain (set
     below) is what makes the bar hold. */
  body.on-home .desk { overflow: visible; }
  /* `body` is a grid with a `1fr` second row: perfect for a fixed-height app shell, useless
     when the page must grow. An auto-height grid gives a `1fr` row no free space to claim, so
     the desk was sized to a fraction of the viewport and everything below it simply overflowed —
     body stayed 391px tall with 1333px of content inside it. On the phone list, normal block
     flow sizes each box to its content and nothing needs a height at all. */
  body.on-home { display: block; overflow: visible; height: auto; min-height: 100%; }
  /* Every box from the desk down becomes plain block flow on the phone list. The desk chain is a
     grid (desk) inside a grid (desk-body) whose columns are flex columns — built so each pane
     can own its own scrollbar in a fixed-height shell. When the page must grow instead, that
     machinery sizes boxes from free space that does not exist and quietly compresses them: the
     rail measured 232px tall with 1209px of cards inside, all of it clipped by `overflow:
     hidden`. A vertical stack of blocks has no such failure mode — each box is as tall as its
     content, and `html` scrolls. */
  /* NOTE: the `.col` selector is deliberately absent. `.sheet` is a `.col`, so including it here
     set `display: block` on the case workspace and the phone list drew the sheet a second time
     under the rail (the sheet's own `display: none` lost on source order). Only the rail's own
     boxes are released. */
  body.on-home .desk,
  body.on-home .desk-body,
  body.on-home .queue,
  body.on-home .rail-body {
    display: block; height: auto; min-height: 0; max-height: none;
    overflow: visible; grid-template-rows: none; grid-template-columns: none;
  }
  /* release the sheet's own clipping *only* while it is the visible pane */
  body:not(.on-home) .sheet,
  body:not(.on-home) .sheet-scroll { height: auto; min-height: 0; }
  body.on-home .queue,
  body.on-home .rail-body { overflow: visible; flex: none; height: auto; }

  /* --- the case is the page, and it has a way out --- */
  body:not(.on-home) .sheet { display: flex; }
  body:not(.on-home) .case-back { display: inline-flex; }   /* the base rule hides it elsewhere */
  /* the workflow bar belongs to the list. Inside a case it is a second navigation competing
     with the case header, and it pushed the header off the first screen. */
  body:not(.on-home) .wfbar { display: none; }
  .case-back { display: none; }

  /* The case view scrolls as ONE region, exactly like the list.
     The desk is a fixed-height app shell where each pane owns its own scrollbar; on a phone that
     left the case as a 10,593px sheet inside a 767px clipped body with no working scroller, so
     the operator could not get past the first screen. Releasing the chain is the fix: every box
     from `html` down becomes as tall as its content, `html` is the scroller, and the sheet's own
     internal scroller stops being one (a nested scroller inside a page scroller is what makes a
     phone page feel stuck). */
  /* `visible` here means "the document scrolls, this box does not clip", which is what the sticky
     bar needs — but it must not extend to X, or a sub-pixel rounding under zoom becomes a
     sideways drag. */
  body.case-open { overflow-x: clip; overflow-y: visible; height: auto; min-height: 100%; display: block; }
  body.case-open .desk,
  body.case-open .desk-body,
  body.case-open .sheet {
    display: block; height: auto; min-height: 0; max-height: none;
    overflow: visible; grid-template-rows: none; grid-template-columns: none;
  }
  /* the sheet's internal scroller is neutralised: the page is the scroller here, and two nested
     scrollers on a touch device fight each other (the inner one swallows the gesture) */
  body.case-open .sheet-scroll {
    display: block; height: auto; min-height: 0; max-height: none; overflow: visible;
  }
  /* the header, the step rail and the actions stay put while the case scrolls under them */
  /* Only what the operator NAVIGATES with stays put: the header (which holds the way back) and
     the step rail (which says where the case is). The step caption is one line of explanation
     about the step — it scrolls away with the content it describes, or it sits on top of the
     agent feed and reads as a rendering fault. */
  body.case-open .case-head,
  body.case-open .stepper {
    position: sticky; z-index: 5; background: var(--surface);
  }
  body.case-open .case-head { top: 0; }
  body.case-open .stepper {
    top: var(--phone-head);
    box-shadow: 0 1px 0 var(--line);   /* the rail's own hairline, so nothing shows through */
  }
  body.case-open .step-caption { position: static; }
  /* Collapsed is a PANEL; expanded is the page.
     Growing the feed with the page made a long reading 30,885px tall, so the case was almost
     entirely agent log and the step card was buried under it. Collapsed is now about half the
     screen with the feed scrolling inside — a panel beside the work, not a wall in front of it.
     The nested scroller is deliberate here: it is bounded and small, and the operator scrolls it
     to read the feed, then scrolls the page to get to the step. The expanded view is where it
     takes the whole screen. */
  body.case-open .chat-wrap { flex: none; }
  body.case-open .chat {
    /* ~half the viewport: enough to read a few exchanges, not enough to bury the step. Divided by
       `--zoom` for the same reason as the expanded height — viewport units scale with it. */
    max-height: calc(44dvh / var(--zoom, 1.1));
    overflow-y: auto;
    overscroll-behavior: contain;
    min-height: 0;
  }
  /* the composer is the input the operator types in: keep it reachable at the foot of the feed */
  body.case-open .chat-compose { position: sticky; bottom: 0; z-index: 4;
    background: var(--surface); border-top: 1px solid var(--line); }
  /* Expanded is the exception: there the feed fills the screen and scrolls within itself, which
     is the point of the control. That needs a bounded height, so the shell goes back to a
     viewport-height column and the feed takes what is left of it. */
  /* `zoom` on :root scales viewport units as well, so a plain `100dvh` here computes to 928px on
     an 844px screen and the feed never fills it. Divide the factor back out — `--zoom` is the one
     number, so this follows a change to it. */
  body.case-open.chat-expanded {
    height: calc(100dvh / var(--zoom, 1.1));
    overflow: hidden;
    display: flex; flex-direction: column;
  }
  body.case-open.chat-expanded .desk,
  body.case-open.chat-expanded .desk-body { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden; }
  body.case-open.chat-expanded .sheet { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden; }
  body.case-open.chat-expanded .sheet-scroll { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden; }
  /* the header and the step rail are part of the expanded screen: they are how the operator
     leaves and how they know where the case is. They keep their natural height. */
  body.case-open.chat-expanded .case-head,
  body.case-open.chat-expanded .stepper { flex: none; }

  body.case-open.chat-expanded .chat-wrap { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  body.case-open.chat-expanded .chat {
    flex: 1 1 auto; min-height: 0; overflow-y: auto;
    max-height: none;                 /* expanded: the whole screen, no cap */
  }
  body.case-open.chat-expanded .stage { display: none; }
  /* Expanded is the activity ALONE, filling the screen. The step rail is navigation for a view
     the operator has just left, and the explanatory caption goes with it — together they were
     457px of chrome on an 844px screen, which is why "expanded" came out barely bigger than the
     collapsed panel. What stays is the minimum to get out: the way back and the case title. */
  /* The app bar ("Mission Control" + the theme toggle) is the console's own furniture, not the
     case's. In expanded view it is 67px of chrome the activity needs: hide it, and the only
     things above the feed are the way back and the title. */
  body.case-open.chat-expanded .bar { display: none; }
  body.case-open.chat-expanded .stepper,
  body.case-open.chat-expanded .step-caption,
  body.case-open.chat-expanded .banner { display: none; }
  body.case-open.chat-expanded .case-head {
    padding: 4px var(--s3);
    border-bottom: 1px solid var(--line);
  }
  body.case-open.chat-expanded .case-head .case-id,
  body.case-open.chat-expanded .case-head .case-actions,
  body.case-open.chat-expanded .case-head .btn-icon { display: none; }
  body.case-open.chat-expanded #case-name { font-size: 17px; }
  /* the feed's own head: a label and the control that returns, nothing else */
  body.case-open.chat-expanded .chat-head { padding: 6px var(--s3); }
  body.case-open.chat-expanded .chat-head .chat-note { display: none; }
  /* and the composer is the last thing at the foot of the screen */
  body.case-open.chat-expanded .chat-compose {
    position: static; border-top: 1px solid var(--line);
    padding-bottom: max(var(--s2), env(safe-area-inset-bottom));
  }
  html:has(body.case-open.chat-expanded) { overflow: hidden; height: 100%; }

  /* --- the workflow bar on a phone ---
     Three workflows and a search used to sit in the bar as tabs plus a full-width field, which
     cost three rows before any case. They collapse to two controls: a three-line button that
     names the current workflow and opens all three, and a search toggle that reveals the field
     only when wanted. Both are phone-only; the desktop keeps its inline tabs. */
  .wfbar {
    flex-direction: row; align-items: center; justify-content: space-between;
    flex-wrap: nowrap; gap: var(--s2);
    padding: var(--s2) max(var(--s3), env(safe-area-inset-left)) var(--s2)
             max(var(--s3), env(safe-area-inset-right));
    position: sticky; top: 0; z-index: 6;
  }
  /* the three-line control carries the current workflow's name, so the bar still answers
     "which tab am I in" without the tabs being visible */
  .wf-menu {
    display: inline-flex; align-items: center; gap: 8px;
    flex: 1 1 auto; min-width: 0; min-height: 46px; padding: 0 14px;
    font: inherit; font-size: 15px; font-weight: 660; color: var(--ink);
    background: var(--raised); border: 1px solid var(--line-2); border-radius: var(--r-md);
    cursor: pointer; text-align: left;
  }
  .wf-menu:active { background: var(--hover); }
  .wf-menu .glyph-svg { flex: none; color: var(--ink-2); }
  .wf-menu-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .wf-menu-n {
    margin-left: auto; flex: none; font-size: 12px; color: var(--ink-3);
    background: var(--sunken); border-radius: var(--r-pill); padding: 1px 8px;
  }
  .wf-search-toggle {
    display: inline-flex; align-items: center; justify-content: center; position: relative;
    flex: none; width: 46px; height: 46px;
    color: var(--ink-2); background: var(--raised);
    border: 1px solid var(--line-2); border-radius: var(--r-md); cursor: pointer;
  }
  .wf-search-toggle[aria-expanded="true"] {
    color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line);
  }
  /* a dot on the toggle when a search is live and the field is folded away, so a filtered list
     can never look like a short list */
  .wf-st-dot {
    position: absolute; top: 7px; right: 7px; width: 8px; height: 8px;
    border-radius: 50%; background: var(--accent); border: 2px solid var(--raised);
  }
  /* the tabs themselves leave the bar — they are offered by the three-line sheet instead */
  .wfbar .wf-tabs { display: none; }
  .wf-tools {
    display: none; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s2); width: 100%;
  }
  /* the search toggle unfolds the tool row; the creates come with it, because they are the same
     question ("this tab: find one, or start one") */
  .wfbar.tools-open { flex-wrap: wrap; }
  .wfbar.tools-open .wf-tools { display: grid; }
  .search-wrap { grid-column: 1 / -1; }
  .search { width: 100%; min-height: 46px; font-size: 16px; }
  #create-hague, #create-informal, #create-translate {
    width: 100%; min-width: 0; min-height: 46px; padding: 0 10px;
    font-size: 13.5px; white-space: nowrap;
  }
  #create-translate { grid-column: 1 / -1; }

  /* the three-line menu's sheet. `display` must be restored here: the default rule hides it on
     the desk, and the phone block otherwise only positioned it — so the sheet stayed invisible
     while the JS believed it was open. */
  .wf-sheet-wrap { display: block; position: fixed; inset: 0; z-index: 40; }
  .wf-sheet-wrap[hidden] { display: none; }
  .wf-sheet-scrim {
    position: absolute; inset: 0; background: rgba(12, 14, 16, 0.42);
    backdrop-filter: blur(1.5px);
  }
  .wf-sheet {
    position: absolute; left: 0; right: 0; bottom: 0;
    background: var(--surface); border-top: 1px solid var(--line-2);
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    box-shadow: var(--e3);
    padding: var(--s2) var(--s3) max(var(--s4), env(safe-area-inset-bottom));
    max-height: 78vh; overflow-y: auto;
  }
  .wf-sheet-grab {
    width: 40px; height: 4px; margin: 6px auto var(--s3);
    border-radius: var(--r-pill); background: var(--line-2);
  }
  .wf-sheet-head {
    margin: var(--s2) 0 6px; padding: 0 var(--s2);
    font-size: 11px; font-weight: 660; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--ink-3);
  }
  .wf-sheet-list { list-style: none; margin: 0 0 var(--s3); padding: 0; }
  .wf-sheet-list li { margin: 0; }
  .wf-sheet-btn {
    display: flex; align-items: center; gap: var(--s3); width: 100%;
    min-height: 54px; padding: 0 var(--s2);
    font: inherit; font-size: 16px; font-weight: 600; color: var(--ink); text-align: left;
    background: transparent; border: 0; border-radius: var(--r-md); cursor: pointer;
  }
  .wf-sheet-btn:active { background: var(--hover); }
  /* THE DRAWER'S OWN GREY (same rule as the tab strip): a blanket-blocked workflow's entry. The
     reason is on the button's title. */
  .wf-sheet-btn.wf-sheet-blocked { color: var(--ink-4); opacity: .55; }
  .wf-sheet-btn.wf-sheet-blocked .wf-sheet-tick { color: var(--ink-4); }
  .wf-sheet-blocked-note {
    display: flex; align-items: center; width: 100%; min-height: 54px; padding: 0 var(--s2);
    font: inherit; font-size: 15px; color: var(--ink-3); text-align: left;
  }
  .wf-sheet-btn .wf-sheet-tick { flex: none; width: 18px; color: var(--accent); }
  .wf-sheet-btn .wf-sheet-n {
    margin-left: auto; flex: none; font-family: var(--mono); font-size: 13px; color: var(--ink-3);
  }
  .wf-sheet-btn[aria-current="true"] { color: var(--accent); background: var(--accent-soft); }
  .wf-sheet-btn[aria-current="true"] .wf-sheet-n { color: var(--accent); }

  /* --- the list: the desk's categorised cards, made for a phone ---
     The desk's centre view groups a tab into the operator's folders, then by destination
     country, and draws each case as a card. On a phone that grouping is the whole point — it is
     how 20 cases stay scannable — so the phone shows the same cards in one column instead of
     the bare numbered rail list. `renderRail` builds the same markup here; the grouping comes
     from `caseSections`, so the two views cannot drift. */
  /* the three-line control already names the current workflow, so the rail's own heading would
     say it a second time one row below. The count is useful; the word is not. */
  .queue .col-head { display: none; }
  .rail-body { padding: 0 max(var(--s3), env(safe-area-inset-left)); }
  .queue-list { list-style: none; padding: 0; gap: 0; }

  .home-section { margin: 0 0 var(--s4); }
  .home-section-head {
    /* Not sticky. Sticky looked helpful (the group name stays visible) but a long destination
       name wraps to two lines at this width and the pinned box then covered the first card,
       hiding the case name. The bar above is already pinned; a second pinned thing that can
       change height is a defect waiting to happen. */
    position: static;
    margin: var(--s3) 0 var(--s2); padding: 0;
    background: none;
  }
  .home-section-head h2 { font-size: 12px; letter-spacing: 0.07em; }
  .home-count { font-size: 12px; }

  .home-cards { grid-template-columns: minmax(0, 1fr); gap: var(--s2); }
  .home-card {
    padding: var(--s3); gap: 10px;
    border-radius: var(--r-lg);
  }
  /* the whole card is the tap target, so the actions sit in their own row beside it rather than
     overlapping the name or the chip */
  .home-card-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 4px; }
  .home-card-main {
    display: flex; flex-direction: column; gap: 8px; align-items: stretch;
    min-width: 0; min-height: 56px; padding: 4px; text-align: left;
    background: transparent; border: 0; border-radius: var(--r-md); cursor: pointer;
  }
  .home-card-main:active { background: var(--hover); }
  .home-card .entry-name { font-size: 16px; font-weight: 660; }
  .home-card-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; }
  .home-card-meta .entry-sub {
    font-size: 13px; color: var(--ink-3); min-width: 0;
    white-space: normal; overflow-wrap: anywhere;
  }
  .home-card .state { font-size: 11px; padding: 3px 8px; flex: 0 1 auto; min-width: 0; max-width: 100%; }
  .home-card .entry-idx { font-size: 12px; }
  /* no hover on a phone, so the pair is always visible (the desk keeps it on hover only).
     Side by side, not stacked: a vertical pair was 101px tall and stole the card's width. */
  .home-card-acts { display: flex; flex-direction: row; gap: 2px; opacity: 1; align-items: flex-start; }
  .home-card-acts .btn-icon { width: 44px; height: 44px; }
  .home-card-acts .btn-icon .glyph { width: 18px; height: 18px; }
  /* the folder select is a real control, so it gets a real row rather than being squeezed
     beside the title */
  .home-card .home-cat-sel { width: 100%; min-height: 44px; }
  .home-empty { padding: var(--s4) var(--s3); }

  /* --- the case sheet --- */
  .case-head {
    padding: var(--s2) max(var(--s4), env(safe-area-inset-left)) var(--s3);
    gap: 2px var(--s2);
    align-items: center;
  }
  /* the title takes the width the back control does not use, on the same line */
  .case-title { flex: 1 1 auto; min-width: 0; }
  .case-id { font-size: 11px; }
  /* The state chip and Delete case were a second full-width row under the title, and the stepper
     sticks at the height of the FIRST row — so those two stayed on screen and peeked out from
     under the step rail while the page scrolled. On a phone the header is ONE row: the way back,
     the title, and the chip. Delete case moves to the foot of the case, where a destructive action
     belongs anyway (out of the way, not beside the title you are reading). */
  .case-head { flex-wrap: nowrap; }
  .case-actions {
    width: auto; flex: 0 0 auto; gap: var(--s2); margin-top: 0;
    align-items: center;
  }
  .case-actions .state { padding: 4px 9px; font-size: 11px; }
  .case-actions #delete-case { display: none; }
  /* The phone header is ONE row of a known height, so the step rail can be offset by exactly
     that and never overlap it. Without a fixed height the header and the rail overlap and the
     title and status chip show through the rail as the page scrolls. `--phone-head` is that
     height: the back control is 44px, plus the header's own vertical padding. */
  :root { --phone-head: 56px; }
  body.case-open .case-head {
    height: var(--phone-head);
    min-height: var(--phone-head);
    max-height: var(--phone-head);
    padding-top: 0; padding-bottom: 0;
    align-items: center;
    overflow: hidden;                /* a fixed header must clip, not grow */
  }
  /* one row means the name gets one line: a two-line title made the header 126px and no fixed
     rail offset could match it */
  body.case-open .case-head .case-id { display: none; }
  body.case-open .case-title { overflow: hidden; }
  body.case-open #case-name {
    font-size: 17px; line-height: 1.2;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* The title and the chip cannot share one 390px row: the chip needs ~133px for a readable
     status, which left the name "New matt…". The chip moves to its own strip directly under the
     header, so the title has the full row and the status keeps all its words. */
  body.case-open .case-actions { min-width: 0; flex: 0 1 auto; }
  /* One row is what makes the rail's offset exact, so the header stays one row and a very long
     case name ellipsises rather than pushing the status off. The chip is capped and the title
     takes the remainder; both are readable, neither pushes the other out. */
  body.case-open .case-head { flex-wrap: nowrap; }
  body.case-open #case-name { min-width: 0; }
  /* "AWAITING APPROVAL" is 17 characters of uppercase with wide letter-spacing: 157px of a 390px
     row, which left the case title "Ne…". Tighten the chip rather than truncate either of them —
     the status still reads, and the title keeps its room. */
  body.case-open .case-actions .state {
    max-width: 46vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 10px; letter-spacing: 0.02em; padding: 4px 7px;
  }
  body.case-open .case-title { flex: 1 1 auto; min-width: 0; }
  body.case-open .case-name-wrap { min-width: 0; }
  /* the destructive action gets its own place at the end of the case, on every step. This block is
     inside the phone media query, so it cannot reach the desk — where the header keeps the button. */
  body.case-open .danger-foot { display: flex; }
  .danger-foot {
    display: none;                    /* the desk keeps the button in the header */
    margin-top: var(--s4); padding-top: var(--s3);
    border-top: 1px solid var(--line);
    justify-content: flex-end;
  }
  .case-back {
    order: -1; flex: 0 0 auto;
    align-items: center; gap: 4px;
    min-height: 44px; padding: 0 12px 0 8px; margin-left: -8px;
    font: inherit; font-size: 14px; font-weight: 620;
    color: var(--accent); background: transparent; border: 0; border-radius: var(--r-md);
    cursor: pointer;
  }
  .case-back:hover, .case-back:active { background: var(--accent-soft); }
  .case-back .glyph-svg { width: 16px; height: 16px; }
  /* THE WORD "CASES" COSTS THE TITLE ITS ROW. Measured at 390px: back control (44px) + chip cap
     (46vw) + paddings left the name "New m…" — the label is 45px of a row the title is starving
     for, and on a phone the arrow alone says "back": this button sits top-left, which is where
     back lives, and its aria-label already says where the press goes. The label span stays in
     the DOM (no markup change, wider phones keep it via the reverse rule below). */
  .case-back-label { display: none; }
  .case-back { padding: 0 10px 0 6px; margin-left: -6px; }
  #case-name { font-size: 19px; }
  .case-actions { gap: var(--s2); }
  .case-actions .btn { min-height: 44px; }

  /* the step rail: four equal tappable cells. The label stays — a bare numeral reads as a
     mystery tick, and the operator mid-task needs "Print", not "3". */
  .stepper {
    padding: 0 max(var(--s4), env(safe-area-inset-left)) 0;
    gap: 4px; overflow-x: auto; scrollbar-width: none;
  }
  .stepper::-webkit-scrollbar { display: none; }
  .step {
    flex: 1 1 0; min-width: 0; min-height: 48px;
    flex-direction: column; align-items: center; justify-content: center; gap: 1px;
    padding: 4px 2px;
  }
  .step-n { font-size: 11px; }
  .step-l { display: block; font-size: 11px; }
  .step-caption { font-size: 13px; padding: 0 max(var(--s4), env(safe-area-inset-left)); }

  .stage { padding: var(--s3) max(var(--s4), env(safe-area-inset-left)) var(--s5); }
  .step-card-head h3 { font-size: 17px; }
  .step-card-head p { font-size: 14px; }

  /* buttons: thumb-sized everywhere, without letting a long label wrap to two lines */
  .btn { min-height: 44px; padding-top: 0; padding-bottom: 0; }
  .btn.sm, .btn.ghost.sm { min-height: 40px; }
  /* action rows read as a small grid: each button is at least half the width, so a row of
     four is two comfortable columns rather than four ragged ones */
  .stage-btnrow { gap: var(--s2); }
  .stage-btnrow .btn { flex: 1 1 calc(50% - var(--s2)); min-width: 0; }
  .stage-btnrow .btn.primary:only-child { flex: 1 1 100%; }

  /* the field sheet: a value is read, not scanned in columns */
  .row { grid-template-columns: 1fr; gap: 4px; }
  .row .label { font-size: 12px; }
  .row textarea, .row input[type=text] { font-size: 16px; }   /* 16px stops iOS zooming on focus */
  .group-head { padding-left: max(var(--s4), env(safe-area-inset-left)); }

  /* the intake file list: name on its own line, controls on the next */
  .filerow {
    grid-template-columns: 1fr auto; gap: 6px 8px;
    padding: 12px max(var(--s4), env(safe-area-inset-left));
  }
  .filerow .fkind { grid-column: 1; }
  .filerow .fname { grid-column: 1 / -1; font-size: 14px; }
  .filerow .flabel-cell { grid-column: 1 / -1; }
  .filerow .fdel { grid-column: 2; grid-row: 1; min-height: 40px; padding: 0 12px; }
  .filerow .flabel-edit, .filerow .flabel { font-size: 14px; }
  .filerow .flabel-save { min-height: 40px; }

  /* the chat feed: full width, and its composer stays above the keyboard */
  .chat-head { padding: 0 max(var(--s4), env(safe-area-inset-left)) var(--s2); }
  .chat { padding: 0 max(var(--s4), env(safe-area-inset-left)); }
  .chat-compose {
    padding: var(--s2) max(var(--s4), env(safe-area-inset-left))
             max(var(--s3), env(safe-area-inset-bottom));
  }
  .chat-row { gap: var(--s2); }
  .chat-row textarea { font-size: 16px; min-height: 44px; }
  .chat-row .btn { min-height: 44px; }
  .bubble { max-width: 88%; }

  /* the print set: one copy after the other, each row thumb-reachable */
  .pset-item {
    grid-template-columns: 18px 20px minmax(0, 1fr) auto 40px;
    gap: 6px; padding: 10px;
  }
  .pset-grip { font-size: 16px; }
  .pset-title { font-size: 14px; }
  .pset-sub { font-size: 12px; }
  .pset-x { width: 40px; height: 40px; }
  .pset-excluded .btn { min-height: 40px; }
  .pset-actions .btn { flex: 1 1 100%; }

  /* the printer picker and the translate row stack */
  .printer-row { flex-wrap: wrap; gap: 6px; padding: 12px; min-height: 56px; }
  .printer-name { margin-left: 0; flex: 1 1 100%; }
  .printer-addr { flex: 1 1 100%; }
  .tr-row .btn { flex: 1 1 100%; }
  .tr-lang { flex: 1 1 100%; min-height: 44px; font-size: 16px; }

  /* cards in the list view become full-width rows */
  .home-cards { grid-template-columns: 1fr; }
  .home-card { padding: 12px; }
  .home-card-main .entry-name { font-size: 15px; }
  .home-cat-sel { min-height: 40px; }
  .wf-intro { margin-bottom: var(--s4); }
  .wf-intro h2 { font-size: 18px; }

  /* overlays: full-bleed, and the sheet's own scroll is the only one */
  .form-overlay, .src-overlay { padding: 0; }
  .form-overlay-head, .src-overlay-head {
    padding: var(--s2) max(var(--s3), env(safe-area-inset-left));
    gap: var(--s2);
  }
  .form-overlay-head h2, .src-overlay-head h2 { font-size: 15px; }
  .fo-actions .btn, .so-title + .fo-actions .btn { min-height: 40px; }
  .pager { padding: var(--s2) max(var(--s3), env(safe-area-inset-left)); gap: 4px; }
  .pg { min-width: 44px; min-height: 44px; }
  .form-overlay-body, .src-overlay-body {
    padding: 0 max(var(--s2), env(safe-area-inset-left))
             max(var(--s3), env(safe-area-inset-bottom));
  }

  /* dialogs: bottom sheet behaviour, so they land under the thumb */
  .modal-wrap { align-items: flex-end; padding: 0; }
  .modal {
    max-width: none; width: 100%;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    padding: var(--s4) var(--s4) max(var(--s4), env(safe-area-inset-bottom));
    max-height: 88vh; overflow-y: auto;
    overscroll-behavior: contain;
  }
  .modal h3 { font-size: 18px; }
  .modal-actions { gap: var(--s2); }
  .modal-actions .btn { flex: 1 1 0; min-height: 46px; }
  #nc-create { flex: 2 1 0; }
  .nc-row { grid-template-columns: 1fr auto; gap: 6px 8px; padding: 10px; }
  .nc-row .nc-file { grid-column: 1 / -1; }
  .nc-row .nc-label { grid-column: 1; min-height: 44px; font-size: 16px; }
  .nc-row .nc-remove { grid-column: 2; min-height: 44px; padding: 0 12px; }
  .nc-docs-head { flex-wrap: wrap; gap: var(--s2); }
  .nc-drop { min-height: 64px; display: flex; align-items: center; justify-content: center; }

  /* The top bar was authored as a desktop bar: mark, queue, engine, profile, theme. At 390px
     that is three lines of chrome before any work happens. On a phone the queue count is the
     fact the operator needs ("which of these needs me"), the theme toggle is reachable, and
     "engine hermes · agent" is a constant nobody reads mid-task. */
  .bar {
    height: auto; flex-wrap: nowrap; align-items: center;
    gap: var(--s2); padding: var(--s2) max(var(--s3), env(safe-area-inset-left));
  }
  .mark { flex: 0 1 auto; min-width: 0; }
  .mark-text { min-width: 0; }
  .mark-name {
    font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .mark-rose { width: 22px; height: 22px; }
  .bar-meta { flex: 0 1 auto; gap: var(--s2); align-items: center; }
  .bar-meta .kv { display: none; }
  .bar-meta { min-width: 0; overflow: hidden; }
  .bar-meta .kv:has(#counts) { display: inline-flex; min-width: 0; max-width: 100%; }
  .bar-meta .kv:has(#counts) .k { display: none; }   /* "queue" is implied by a count */
  #counts { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* at this width the long queue sentence is clipped, so swap in the short form */
  #counts-short { display: inline; font-size: 12px; white-space: nowrap; }
  .bar-meta .kv:has(#counts) > #counts { display: none; }
  .mark-sub { display: none; }      /* the tabs say it; the mark keeps its name */
  .bar-tools { flex: none; }
  .btn.ghost.icon { width: 44px; height: 44px; }
}

/* Very narrow phones (iPhone SE and similar): three tab labels cannot hold 13px plus a count
   badge in 320px, and "Translations" was clipping mid-word. Tighten the tab row rather than
   truncate a label — a tab whose name is cut off is a defect, not a design. */
@media (max-width: 380px) {
  /* the tab row gets the whole width minus the bar padding, and may scroll as a last resort:
     a tab that cannot fit must still be reachable rather than clipped */
  .wf-tabs { overflow-x: auto; scrollbar-width: none; }
  .wf-tabs::-webkit-scrollbar { display: none; }
  .wf-tab { font-size: 11.5px; padding: 0 2px; gap: 2px; letter-spacing: -0.01em; }
  .wf-tab .wf-n { font-size: 11px; }  /* the count is data, not decoration: the 11px floor holds */
  /* In a 320px row the index (26px) and two thumb-sized actions (110px) leave ~117px for the
     content, and a nowrap "Awaiting approval" pill is 168px — it was being cut off by the
     icons. The pill may wrap to two short lines here; keeping the 44px targets matters more
     than keeping the label on one line. */
  .entry-meta .state, .home-card-meta .state {
    font-size: 11px; padding: 3px 8px;
    white-space: normal; overflow-wrap: anywhere;
    /* the desktop rule is `flex: none`, which pins the pill at its content width no matter how
       narrow the column is; a flex item must be allowed to shrink before it can wrap */
    flex: 0 1 auto; min-width: 0; max-width: 100%;
  }
  .entry-main { min-width: 0; }
  #create-hague, #create-informal, #create-translate { font-size: 12.5px; padding: 0 8px; }
  .case-actions .state { font-size: 11px; padding: 4px 8px; }
  .case-actions .btn { padding: 0 10px; font-size: 13px; }
  /* at 380px even the icon-only back control's row is short: give the name every recovered
     pixel — the chip cap (46vw) and the title now share the row with ~34px of chrome */
  body.case-open .case-actions .state { max-width: 44vw; }
}

/* a phone held sideways has height to spare and width to spend: back to two panes is wrong
   (there is still not room for a rail), so keep the drill-down and just use the width */
@media (max-width: 720px) and (orientation: landscape) and (min-width: 560px) {
  .wfbar { flex-direction: row; align-items: center; flex-wrap: wrap; }
  .wf-tabs { flex: 1 1 100%; }
  .search-wrap { flex: 1 1 18ch; }
  #create-hague, #create-informal, #create-translate { flex: 0 1 auto; padding: 0 14px; }
  .stage, .sheet-scroll { max-width: 92ch; margin: 0 auto; width: 100%; }
}

/* --------------------------------------------------- the agent feed on a phone
 *
 * The row head is icon + kind + title + meta + time + toggle on ONE line. At 390px that leaves the
 * title about 90px, so a file path breaks mid-word ("/tmp/nm3/scan_ch" / "eck.py") and the row
 * reads as a rendering fault. On a phone the head becomes two lines: the kind and the title above,
 * the meta, time and toggle below. Nothing is cut, and nothing wraps inside a word.
 */
@media (max-width: 720px) {
  .act-head {
    flex-wrap: wrap; align-items: center;
    gap: 4px var(--s2);
    padding: 8px 10px;
    row-gap: 2px;
  }
  .act-icon { font-size: 13px; }
  .act-title {
    /* own line, full width, and only break a long token if it truly cannot fit */
    flex: 1 1 100%; order: 3;
    font-size: 13px; line-height: 1.45;
    overflow-wrap: break-word; word-break: normal;
    white-space: normal;
  }
  .act-k { order: 1; }
  .act-meta { order: 2; margin-left: auto; font-size: 11px; }
  .act-time { order: 4; font-size: 11px; }
  .act-toggle { order: 5; margin-left: auto; min-height: 34px; padding: 0 12px; }

  .act-body { font-size: 12px; padding: 9px 11px; max-height: 260px; }

  /* A bubble at 85% of a 390px screen leaves a cramped column with dead space beside it, and a
     long path still breaks. Nearly full width, with the operator's own messages kept distinct by
     their colour and alignment rather than by being narrow. */
  .chat-msg { max-width: 100%; }
  .chat-msg.agent { font-size: 13px; padding: 10px 12px; max-width: 96%; }
  .chat-msg.user { max-width: 88%; }
  .chat-msg .msg-text { overflow-wrap: break-word; word-break: normal; }
  /* an activity row's body is monospace output: keep it from forcing a horizontal scroll */
  .act-body { overflow-x: auto; }
  .chat { padding: var(--s3) var(--s3); }
  /* the feed's own header: the heading and the button share a line, the note goes under them.
     Otherwise "Agent activity" breaks to "AGENT" / "ACTIVITY" and looks broken. */
  .chat-head {
    padding: 10px var(--s3);
    flex-wrap: wrap; align-items: center;
    gap: 2px var(--s2);
  }
  .chat-head h2 { flex: 1 1 auto; font-size: 14px; white-space: nowrap; order: 1; }
  .chat-head .btn { order: 2; flex: none; min-height: 36px; padding: 0 12px; }
  .chat-head .chat-note { order: 3; flex: 1 1 100%; font-size: 12px; }
  .chat-compose { padding: var(--s2) var(--s3) max(var(--s2), env(safe-area-inset-bottom)); }
}

/* =========================================================================== touch sizes
 *
 * Gated on phone GEOMETRY, never on `hover`/`pointer`. This desk reports `(hover: none)` too, so
 * a pointer-based query pulled phone target sizes onto the desktop — the two must not share a
 * condition. A width, or the short-and-wide shape of a landscape phone, cannot match a desktop
 * window, so nothing in here can reach the desk.
 */
@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
  /* every control a thumb lands on is at least 44px on its short side */
  .btn { min-height: 44px; }
  .btn.sm { min-height: 40px; }
  .btn.ghost.icon, .btn.icon { width: 44px; height: 44px; }
  .btn-icon { width: 44px; height: 44px; opacity: 1; }
  .wf-tab { min-height: 44px; }
  .wf-menu, .wf-search-toggle { min-height: 44px; }
  .step { min-height: 44px; }
  .pg { min-width: 44px; min-height: 44px; }
  select, .cat-sel, .home-cat-sel { min-height: 44px; }
  /* 16px stops iOS zooming the page when a field takes focus */
  input[type=text], input[type=email], input[type=search], select, textarea { font-size: 16px; }
  /* there is no hover to lean on, so give controls a press state instead */
  .btn:active:not(:disabled) { transform: translateY(1px); }
  .entry:active, .wf-tab:active, .printer-row:active, .home-card-main:active { background: var(--hover); }
}

/* A landscape phone is 844px wide: too wide for the phone rules above, so it keeps the desk's
 * two-pane layout (correctly — there is room). It still needs the drill-down, because the rail
 * and the sheet side by side on a 390px-tall screen leaves neither pane usable. */
@media (orientation: landscape) and (max-height: 500px) and (max-width: 900px) {
  body.on-home .sheet { display: none; }
  body:not(.on-home) .queue { display: none; }
  body:not(.on-home) .case-back { display: inline-flex; }   /* the base rule hides it elsewhere */
  body:not(.on-home) .wfbar { display: none; }
  .case-back { display: none; }
  .queue { border-right: 0; }
  /* the rail is the page here too, so it draws the categorised cards, not the index */
  .rail-body { padding: 0 var(--s3); }
  .queue .col-head { display: none; }
  .home-cards { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
  .home-section-head { position: static; margin: var(--s3) 0 var(--s2); padding: 0; }
  .home-card-acts { opacity: 1; }
  .home-card-acts .btn-icon { width: 40px; height: 40px; }
  .home-card-meta .entry-sub {
    font-size: 13px; white-space: normal; overflow-wrap: anywhere; line-clamp: 2;
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  }
  /* the create row is desk-width controls; wrap it so nothing runs past the right edge */
  .wfbar { flex-wrap: wrap; }
  .wf-tools { width: 100%; }
}

/* --------------------------------------------------- the print set's drag affordance
 *
 * Reordering is the operator's own gesture on their own set, so it must read as picking up a row:
 * a grip that invites the drag, a lifted ghost while it moves, and a clear line where it will land.
 */
.pset-item[draggable="true"] { cursor: grab; }
.pset-item[draggable="true"]:active { cursor: grabbing; }
.pset-item.dragging { opacity: .45; }
.pset-item.drop-before,
.pset-item.drop-after { position: relative; }
.pset-item.drop-before::before,
.pset-item.drop-after::after {
  content: ""; position: absolute; left: 0; right: 0; height: 2px;
  background: var(--accent); border-radius: 2px;
}
.pset-item.drop-before::before { top: -1px; }
.pset-item.drop-after::after { bottom: -1px; }
/* the row itself must not flicker while the browser draws the drag image */
.pset-item.dragging .pset-x { pointer-events: none; }

/* ---------------------------------------------------------------- the theme button
 *
 * Three states, one button. The mark shows the state you are IN (not the one you would go to), so
 * the operator can see at a glance whether the console is following their system or overridden:
 *   system — a monitor, "whatever your machine is doing"
 *   light  — a sun
 *   dark   — a half-filled disc
 */
.theme-btn { position: relative; }
.theme-btn .theme-i { display: none; }
.theme-btn[data-theme-state="system"] .theme-i[data-for="system"],
.theme-btn[data-theme-state="light"]  .theme-i[data-for="light"],
.theme-btn[data-theme-state="dark"]   .theme-i[data-for="dark"] { display: block; }
/* on system the button reads as "automatic" rather than as a fixed choice */
.theme-btn[data-theme-state="system"] { color: var(--ink-3); }

/* ------------------------------------------------------------- the court format (E4.1)
 *
 * PROJECT.md §3.2: the formats are the CLIENT'S OWN, held as editable data. This panel shows
 * which one a job is on and, when the system has a view, WHY it recommends one — so the operator
 * can audit the recommendation instead of accepting it (R3). A recommendation is drawn as a
 * warning, never as a settled fact: until the operator confirms it, the job is not on a format.
 */
.fmt-box {
  margin: var(--s3) 0; padding: var(--s3) var(--s4);
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--raised);
}
.fmt-box h4 { margin: 0 0 6px; font-size: 13px; font-weight: 660; }
.fmt-box .pset-note b { color: var(--ink); }
.fmt-quiet { color: var(--ink-4); }
.fmt-warn b { color: var(--warn); }
.fmt-warn { border-left: 3px solid var(--warn-line); padding-left: var(--s3); }
.fmt-bad b { color: var(--danger); }
.fmt-bad { border-left: 3px solid var(--danger-line); padding-left: var(--s3); }
.fmt-row { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s3); }

/* the recommendation: a summary the operator opens, because the reasons are the point */
.fmt-match { margin-top: var(--s3); border-top: 1px solid var(--line); padding-top: var(--s2); }
.fmt-match > summary {
  cursor: pointer; list-style: none; display: flex; align-items: center;
  gap: var(--s2); font-size: 13px; color: var(--ink-2);
}
.fmt-match > summary::-webkit-details-marker { display: none; }
.fmt-match > summary .caret { color: var(--ink-4); transition: transform var(--dur) var(--ease); }
.fmt-match[open] > summary .caret { transform: rotate(90deg); }
.fmt-match-title b { color: var(--accent); }
.fmt-terms {
  margin: var(--s2) 0 4px; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-3); font-weight: 650;
}
.fmt-terms.mono { text-transform: none; letter-spacing: 0; font-size: 12px; }
.fmt-why { margin: 0 0 var(--s2); padding-left: 18px; font-size: 12px; color: var(--ink-2); line-height: 1.6; }
.fmt-why li { margin: 2px 0; }

/* every format the system considered, with the terms that hit and the terms that did not */
.fmt-cand-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s2); }
.fmt-cand {
  border: 1px solid var(--line); border-radius: var(--r-md);
  padding: var(--s2) var(--s3); background: var(--sunken);
}
.fmt-cand.has { border-color: var(--accent-line); background: var(--accent-soft); }
.fmt-cand-name { font-size: 13px; font-weight: 640; color: var(--ink); }
.fmt-cand-score {
  font-family: var(--mono); font-size: 11px; margin-left: var(--s2);
  color: var(--accent); text-transform: uppercase; letter-spacing: 0.06em;
}
.fmt-cand-score.off { color: var(--ink-4); }
.fmt-cand-att { font-size: 11px; color: var(--ink-3); margin-left: var(--s2); }
.fmt-notes { margin: 6px 0 0; padding-left: 16px; font-size: 12px; color: var(--ink-2); line-height: 1.55; }
.fmt-notes li { margin: 1px 0; }

/* the picker: formats by NAME and what they apply to, never by id */
.fmt-picker { margin-top: var(--s3); }
.fmt-pick-list { list-style: none; margin: var(--s2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--s2); }
.fmt-pick-btn {
  width: 100%; text-align: left; font: inherit; cursor: pointer;
  display: flex; flex-direction: column; gap: 3px;
  padding: var(--s2) var(--s3);
  background: var(--sunken); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-md);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.fmt-pick-btn:hover { background: var(--hover); border-color: var(--line-2); }
.fmt-pick-btn[aria-current="true"] { border-color: var(--accent); background: var(--accent-soft); }
.fmt-pick-name { font-size: 13px; font-weight: 640; }
.fmt-pick-meta { font-size: 11px; color: var(--ink-3); overflow-wrap: anywhere; }

/* the editor: the pipeline's own fields, none of them pre-filled (R1) */
.fmt-editor {
  margin-top: var(--s3); padding: var(--s3);
  border: 1px solid var(--line-2); border-radius: var(--r-md);
  background: var(--sunken);
}
.fmt-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--s2); }
.fmt-editor .cf { display: flex; flex-direction: column; gap: 4px; margin: 6px 0; }
.fmt-editor .cf span { font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--ink-3); font-weight: 650; }
.fmt-editor input, .fmt-editor textarea, .fmt-editor select {
  font: inherit; font-size: 13px; padding: 7px 10px;
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
}
.fmt-editor textarea { font-family: var(--mono); font-size: 12px; resize: vertical; }
/* The one CHECKBOX in the format editor: a fact about the court, not a field with a value. The
   label text and the box sit on one line, and the note under them is ordinary prose rather than
   the upper-case field label the other rows carry. */
.fmt-editor .cf-check > span:first-child { text-transform: none; letter-spacing: 0;
                                           font-size: 12px; }
.fmt-editor .cf-check input[type="checkbox"] { width: 16px; height: 16px; padding: 0;
                                               align-self: flex-start; accent-color: var(--accent); }
.fmt-editor .cf-check .pset-note { text-transform: none; letter-spacing: 0; font-weight: 400;
                                   margin: 0; }
.fmt-editor input:focus-visible, .fmt-editor textarea:focus-visible, .fmt-editor select:focus-visible {
  outline: none; border-color: var(--accent); box-shadow: var(--ring);
}

/* ---------------------------------------------- the court-format panel on a phone
 *
 * Phone rules are gated on GEOMETRY only — a width or shape no desktop window has. Never on
 * `hover`/`pointer`: this desk reports `hover: none`, so a pointer query leaks onto the desktop.
 *
 * The panel carries court and county names, which are long, and a candidate list whose rows are
 * a name plus a note per term considered. Left alone, a long `mono` line sets a min-content floor
 * and widens the whole phone page sideways — the trap that has bitten this console before. So the
 * grid tracks get `minmax(0, 1fr)` and the text gets `overflow-wrap: anywhere`, rather than a
 * `max-width` on the children.
 */
@media (max-width: 720px) {
  .fmt-box { padding: var(--s3); }
  .fmt-grid { grid-template-columns: minmax(0, 1fr); }
  .fmt-row { flex-wrap: wrap; }
  .fmt-row .btn { flex: 0 1 auto; min-height: 44px; }
  .fmt-match > summary { align-items: flex-start; }
  .fmt-match-title { min-width: 0; overflow-wrap: anywhere; }
  .fmt-terms.mono { overflow-wrap: anywhere; }
  .fmt-cand, .fmt-notes li, .fmt-pick-btn { min-width: 0; overflow-wrap: anywhere; }
  .fmt-editor .cf { min-width: 0; }
  .fmt-editor input, .fmt-editor textarea, .fmt-editor select { min-height: 44px; }
}

/* a landscape phone has height to spare: two columns is right there, not one */
@media (max-width: 720px) and (orientation: landscape) and (min-width: 560px) {
  .fmt-grid { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
}

/* ============================================================== the attempt log (informal)

   Every attempt is its own card; every READING of that attempt is a row inside it, with the one
   taken marked. A candidate reading must never look like the chosen one, so the effective row
   carries the accent and the others are quiet — the operator is looking for "which of these is
   the one that stands", and colour-blind-quiet is not enough on its own, so each row also says
   so in words.

   Grid tracks are `minmax(0,1fr)` and the long values carry `overflow-wrap`: a file path, a
   record id and an ISO instant are all unbroken tokens, and an unbroken token inside an auto
   track sets a min-content floor that scrolls the whole phone page sideways. */
.att-panel { margin: var(--s4) 0 0; }
.att-panel h3 { margin: 0 0 var(--s2); font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3); font-weight: 680; }
.att-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s3); }
.att-card { min-width: 0; overflow-wrap: anywhere; }
.att-card h4 { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.att-quiet { color: var(--ink-3); font-weight: 400; }
.att-k { display: inline-block; min-width: 62px; font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-3); font-weight: 680; }
.att-read { border: 1px solid var(--line); border-left: 3px solid transparent; border-radius: var(--r-sm); padding: var(--s2) var(--s3); margin: var(--s2) 0 0; background: var(--raised); }
.att-read.on { border-left-color: var(--accent); }
.att-read-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 4px; }
.att-rec { font-size: 11px; color: var(--ink-2); }
.att-tag { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; font-weight: 680; padding: 1px 6px; border-radius: 999px; border: 1px solid var(--accent); color: var(--accent); white-space: nowrap; }
.att-tag.quiet { border-color: var(--line); color: var(--ink-3); }
.att-fields > div { font-size: 13px; color: var(--ink); margin-top: 2px; }
.att-fields .mono { margin-left: 6px; font-size: 11px; }
.att-null { color: var(--ink-3); font-style: italic; }
.att-why, .att-edit { margin: var(--s2) 0 0; font-size: 12px; color: var(--ink-2); border-top: 1px solid var(--line); padding-top: 6px; }
.att-edit { border-top: 0; padding-top: 2px; }
.att-edit .mono { display: block; margin-top: 2px; font-size: 11px; }
.att-flags { margin: var(--s2) 0 0; }
.att-flags li { font-size: 12px; }
.att-gaps { margin-top: var(--s3); }
.att-summary { margin: 0; font-size: 13px; white-space: pre-wrap; color: var(--ink); }

@media (max-width: 720px) {
  .att-k { min-width: 0; display: block; }
  .att-fields .mono { display: block; margin-left: 0; }
  .att-read, .att-card { min-width: 0; }
}
.att-sum-card { margin-top: var(--s3); }

/* ------------------------------------------------------------------ My servers

   The client's own process-server roster, drawn inside the same overlay shell the filled form
   uses. A surface over /api/servers: nothing here decides what a caller may see or change, and
   a refusal is rendered in the server's own words. */

/* Scoped to this overlay: the row controls are `btn ghost`, which in the desk's shell renders as
   near-naked text. Inside a card the actions are the row's primary affordance, so they get a real
   edge here — without touching `.btn` itself, which every other screen depends on. */
#servers-overlay .btn.ghost {
  border-color: var(--line-2); background: var(--raised); color: var(--ink-2);
  box-shadow: var(--e1);
}
#servers-overlay .btn.ghost:hover:not(:disabled) {
  border-color: var(--line-3); background: var(--hover); color: var(--ink);
}
#servers-overlay .btn.ghost:disabled { border-color: var(--line); background: var(--sunken); }
#servers-overlay .btn.sm { height: 30px; }

.srv-caller {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  padding: var(--s3) var(--s4); margin-bottom: var(--s3);
  background: var(--sunken); border: 1px solid var(--line); border-radius: var(--r-md);
}
.srv-caller-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--ink-3); font-weight: 640;
}
.srv-caller select, .srv-caller input {
  font: inherit; font-size: 13px; height: 30px; padding: 0 var(--s2);
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm);
}
.srv-caller input { min-width: 190px; }
.srv-caller input:disabled { color: var(--ink-4); background: var(--sunken); }
.srv-caller-note { font-size: 12px; color: var(--ink-4); }

.srv-hint { font-size: 13px; color: var(--ink-3); margin: 0 0 var(--s3); }
.srv-hint.sm { font-size: 12px; }

.srv-banner {
  padding: var(--s3) var(--s4); margin-bottom: var(--s3);
  border: 1px solid var(--accent-line); background: var(--accent-soft); color: var(--ink);
  border-radius: var(--r-md); font-size: 13px; line-height: 1.5;
  overflow-wrap: anywhere;
}
.srv-banner[data-kind="error"] {
  border-color: var(--danger-line); background: var(--danger-soft); color: var(--danger);
}
.srv-banner[data-kind="info"] {
  border-color: var(--ok-line); background: var(--ok-soft); color: var(--ok);
}
/* A write that LANDED but whose list re-read did not answer, and a roster read that came back
   without an answer: neither is an error (nothing was refused) and neither is good news. The
   amber the console already uses for "not settled / confirm this" is the honest one, so the
   operator can tell it apart from a green acknowledgement and a red refusal at a glance. */
.srv-banner[data-kind="warn"] {
  border-color: var(--warn-line); background: var(--warn-soft); color: var(--warn);
}

.srv-add {
  padding: var(--s4); margin-bottom: var(--s4);
  background: var(--raised); border: 1px solid var(--line-2); border-radius: var(--r-md);
  box-shadow: var(--e1);
}
.srv-add h3 { margin: 0 0 var(--s3); font-size: 15px; font-weight: 680; }
.srv-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--s3); }
.srv-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.srv-field.wide { grid-column: 1 / -1; }
.srv-field span {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--ink-3); font-weight: 640;
}
.srv-field input, .srv-field select {
  font: inherit; font-size: 13px; height: 32px; padding: 0 var(--s2);
  background: var(--raised); color: var(--ink);
  border: 1px solid var(--line-2); border-radius: var(--r-sm); min-width: 0;
}
.srv-field input:focus, .srv-field select:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.srv-add .modal-actions { margin-top: var(--s4); }

.srv-pool { margin-bottom: var(--s5); }
.srv-pool h3 {
  display: flex; align-items: center; gap: var(--s2);
  margin: 0 0 var(--s2); font-size: 13px; font-weight: 680;
  text-transform: uppercase; letter-spacing: 0.07em; color: var(--ink-2);
}
.srv-count { font-size: 12px; color: var(--ink-4); }
.srv-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); }

.srv-item {
  padding: var(--s3) var(--s4); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-md);
}
.srv-item-main { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.srv-name { font-size: 15px; font-weight: 640; color: var(--ink); overflow-wrap: anywhere; }

.srv-pill {
  font-size: 11px; font-weight: 640; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 3px 8px; border-radius: var(--r-pill);
  border: 1px solid var(--line-2); background: var(--sunken); color: var(--ink-3);
}
.srv-pill.on   { border-color: var(--ok-line); background: var(--ok-soft); color: var(--ok); }
.srv-pill.off  { border-color: var(--danger-line); background: var(--danger-soft); color: var(--danger); }
.srv-pill.unset{ border-color: var(--warn-line); background: var(--warn-soft); color: var(--warn); }

.srv-tag { font-size: 12px; color: var(--ink-4); }
.srv-meta { margin: 4px 0 0; font-size: 11px; color: var(--ink-4); overflow-wrap: anywhere; }
.srv-notes { margin: var(--s2) 0 0; font-size: 13px; color: var(--ink-2); overflow-wrap: anywhere; }
.srv-held {
  margin: var(--s2) 0 0; font-size: 12px; line-height: 1.5;
  color: var(--warn); overflow-wrap: anywhere;
}
.srv-held.free { color: var(--ink-4); }
/* The holding could not be READ — a third state, and it must not look like the free row's
   quiet grey: an unknown holding is a reason the removal will be refused (503), so it wears
   the same amber as a live holding, in italics to say the holding is not known. */
.srv-held.unknown { color: var(--warn); font-style: italic; }
.srv-why { margin: 4px 0 0; font-size: 12px; color: var(--ink-3); line-height: 1.5; }

.srv-acts { display: flex; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s3); }
.srv-acts .btn { flex: 0 0 auto; }
.srv-empty { font-size: 13px; color: var(--ink-3); }

/* ===== candidate pools (E1.2): a job's two pools, and which pool each server came from =====

   The rows are deliberately NOT colour-coded by pool alone. A tag reading "platform" or
   "client:acme" is the fact; the accent marks the row that is the CLIENT's own, because that is
   the one whose ownership matters to the operator reading the screen. Colour is never the only
   carrier of a meaning here — every state is also written out, so the panel survives a
   monochrome screen and a screenshot. */

.cand-panel { margin: var(--s4) 0 0; }
.cand-panel .cand-grid { align-items: start; }
.cand-card { min-width: 0; }
.cand-card > .pset-note { margin: 0 0 var(--s2); }
.cand-list { list-style: none; margin: var(--s2) 0 0; padding: 0; }
.cand-row {
  display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap;
  padding: 5px 0; border-top: 1px solid var(--line);
}
.cand-row:first-child { border-top: 0; }
.cand-name { font-size: 13px; color: var(--ink); flex: 1 1 8em; min-width: 0; overflow-wrap: anywhere; }
.cand-pool {
  font-size: 11px; padding: 1px 6px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--ink-2); white-space: nowrap;
}
.cand-pool[data-pool="client"] { border-color: var(--accent); color: var(--accent); }
.cand-av { font-size: 11px; color: var(--ink-3); white-space: nowrap; }
.cand-av[data-av="false"] { color: var(--ink-2); text-decoration: line-through; }
.cand-unresolved { color: var(--ink-2); font-weight: 620; }
.cand-panel .att-flags { margin: var(--s3) 0 0; }

/* ============ dispatch — assign this job to a named server (E2.1) ============
   A MANAGED model: the operator names one server from the two pools the job may draw on. There is
   no bid board and nothing is offered to anybody, so there is no control here for posting, bidding
   or claiming — and the panel says so in words, because an operator who cannot find one needs to
   know it never existed rather than to conclude the screen is broken. */

.disp-panel { margin: var(--s4) 0 0; }
.disp-state { display: block; margin: var(--s2) 0 var(--s3); padding: var(--s2) var(--s3);
              background: var(--sunken); border: 1px solid var(--line-3);
              border-radius: var(--r-sm); font-size: 12px; overflow-wrap: anywhere; }
.disp-state[data-state="assigned"] { border-color: var(--ok-line); background: var(--ok-soft); }
.disp-state[data-state="assigned_missing"] { border-color: var(--danger); }
.disp-state[data-state="unknown"] { border-color: var(--danger); }
.disp-who { min-width: 0; }
.disp-who.disp-none { /* no bar: "nobody assigned" is a state, not a severity */ }
/* `.req-card .mono` is a BLOCK (label-value pairs need it), and this one sentence carries two mono
   values INLINE. Left as blocks, each becomes its own line and the sentence breaks into orphaned
   fragments with a lone "." between them — measured on a rendered shot. Scoped to this one
   paragraph so the block behaviour the rest of the card relies on is untouched. */
.disp-who p.disp-then .mono { display: inline; margin-top: 0; }
.disp-k { display: inline-block; min-width: 0; color: var(--ink-3);
          text-transform: uppercase; letter-spacing: .04em; font-size: 10px;
          margin-right: var(--s2); }
.disp-act { margin: var(--s3) 0 0; min-width: 0; }
.disp-label { display: block; margin: var(--s2) 0 3px; font-size: 11px; color: var(--ink-3);
              text-transform: uppercase; letter-spacing: .04em; }
.disp-pick, .disp-instruction { width: 100%; max-width: 100%; min-width: 0;
                                font-family: var(--mono); font-size: 13px; }
.disp-act .btn { margin-top: var(--s3); }
/* `.disp-unassign` has no bar: unassigning is an action's outcome, not a severity. */
.disp-unassign { padding-left: var(--s2); }
.disp-blocked .notes { margin: var(--s2) 0 0; }
.disp-blocked li { overflow-wrap: anywhere; }
.disp-hist { margin: var(--s3) 0 0; }
.disp-hist > summary { cursor: pointer; font-size: 12px; color: var(--ink-2); overflow-wrap: anywhere; }
.disp-hist-list { list-style: none; margin: var(--s2) 0 0; padding: 0;
                  display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s2); }
.disp-hist-row { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline;
                 border-left: 2px solid transparent; padding-left: var(--s2); }
.disp-hist-row.in-force { border-left-color: var(--accent); }  /* only the in-force row carries the bar */
.disp-hist-act { text-transform: uppercase; font-size: 10px; letter-spacing: .04em;
                 color: var(--ink-3); }
.disp-hist-name { color: var(--ink); overflow-wrap: anywhere; }
.disp-hist-instr { overflow-wrap: anywhere; }
.disp-chip { font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
             padding: 1px 6px; border-radius: var(--r-pill);
             background: var(--accent-soft); color: var(--accent);
             border: 1px solid var(--accent-line); }

/* THE JOB'S STATUS (E2.2). The chip a step carries is the whole point of the panel: "reached",
   "not reached" and "not established" are three DIFFERENT facts about a job and are drawn as
   three different things. A step the records do not settle is neither ticked nor blanked, because
   those read as "done" and "not done" and neither is what the record says. */
.st-panel { margin: var(--s4) 0 0; }
.st-where { overflow-wrap: anywhere; }
.st-now { overflow-wrap: anywhere; }
.st-steps { list-style: none; margin: var(--s3) 0 0; padding: 0;
            display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s2); }
.st-step { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline;
           border-left: 2px solid transparent; padding-left: var(--s2); min-width: 0; }
/* the bar appears only when a step's state says something: reached (ok), unknown (amber),
   current (accent). An unreached step is quiet — grey bars on every row drowned the timeline. */
.st-step[data-reached="true"] { border-left-color: var(--ok-line); }
.st-step[data-reached="unknown"] { border-left-color: var(--amber, #c98a00); }
.st-step.current { border-left-color: var(--accent); background: var(--sunken); }
.st-step-n { font-size: 10px; color: var(--ink-3); min-width: 12px; }
.st-step-label { font-weight: 640; overflow-wrap: anywhere; }
.st-step-when { overflow-wrap: anywhere; }
/* The REASON a step is not established is the text that must be readable, so it is a full-width
   line under the row rather than a tooltip or a truncation — an operator who cannot see why a
   step is unsettled would read the chip as a fault in the job. */
.st-step-why { flex: 1 0 100%; color: var(--ink-3); font-size: 11px; overflow-wrap: anywhere; }
.st-chip { font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
           padding: 1px 6px; border-radius: var(--r-pill);
           border: 1px solid var(--line-3); color: var(--ink-3); background: var(--sunken); }
.st-chip-done { border-color: var(--ok-line); background: var(--ok-soft); color: var(--ink-2); }
.st-chip-current { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent); }
.st-chip-unknown { border-color: var(--amber, #c98a00); }
.st-moves { margin: var(--s3) 0 0; }
.st-move { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline;
           border-left: 2px solid transparent; padding-left: var(--s2); }
.st-move-act { text-transform: uppercase; font-size: 10px; letter-spacing: .04em; color: var(--ink-3); }
.st-move-to { overflow-wrap: anywhere; }
.st-move-note { overflow-wrap: anywhere; }
.st-gaps li { overflow-wrap: anywhere; }

@media (max-width: 720px) {
  .st-step, .st-move { display: block; }
  .st-step > *, .st-move > * { display: block; }
}

@media (max-width: 720px) {
  /* A label, a value and the action row cannot share a 390px line. The value goes on its own
     line under its label, and the two buttons take the full width they need. */
  .disp-k { display: block; min-width: 0; }
  .disp-act .btn { width: 100%; }
  .disp-hist-row { display: block; }
  .disp-hist-row > * { display: block; }
}

@media (max-width: 720px) {
  /* A row's three parts cannot share a 390px line: the name would be squeezed to nothing and a
     server name would wrap mid-word. Name on its own line, the two tags beneath it. */
  .cand-row { display: block; }
  .cand-name { display: block; margin-bottom: 3px; }
  .cand-pool, .cand-av { display: inline-block; margin-right: var(--s2); }
}

/* ============ informal workflow step card (E8.1) ============
   The card is drawn from the machine the server sends; these are only its clothes. Nothing here
   decides anything about the sequence. */

.informal-status { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center;
                   margin: var(--s2) 0 var(--s3); }
#informal-status-key { font-size: 12px; color: var(--ink-2); background: var(--sunken);
                       border: 1px solid var(--line-3); border-radius: var(--r-sm);
                       padding: 2px 7px; }
.informal-flag { font-size: 11px; font-weight: 640; letter-spacing: 0.06em; text-transform: uppercase;
                 color: var(--accent); background: var(--accent-soft);
                 border: 1px solid var(--accent-line); border-radius: var(--r-pill);
                 padding: 2px 8px; }

.informal-acts { list-style: none; margin: var(--s3) 0 0; padding: 0; display: flex;
                 flex-direction: column; gap: var(--s2); }
.informal-act { display: flex; gap: var(--s3); align-items: baseline; flex-wrap: wrap; }
/* the note beside a control explains what it will do — it must be readable at phone width, so it
   takes its own line rather than being clipped beside the button */
.informal-note { font-size: 12px; color: var(--ink-3); flex: 1 1 18ch; min-width: 0;
                 overflow-wrap: anywhere; }

.informal-blocked, .informal-absent-box { margin-top: var(--s4); padding: var(--s3) var(--s4);
  border: 1px solid var(--line-3); border-left: 3px solid var(--warn-line);
  border-radius: var(--r-md); background: var(--surface-2, var(--sunken)); }
/* the AMBER hex was a literal outside the palette; `--warn-line` is the same "confirm this"
   language on the token the theme owns. `informal-absent-box` overrides to grey: absence of a
   document is a fact, not a warning. */
.informal-absent-box { border-left-color: var(--line-3); }
.informal-blocked h4, .informal-absent-box h4 {
  margin: 0 0 var(--s2); font-size: 11px; text-transform: uppercase; letter-spacing: 0.09em;
  color: var(--ink-3); font-weight: 660;
}
.informal-blocked ul, .informal-absent-box ul { list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--s3); }
.informal-stop { display: flex; flex-direction: column; gap: 3px; }
.informal-stop-label { font-size: 13px; font-weight: 600; color: var(--ink-2); }
/* the server's own reason, verbatim: a long sentence must wrap rather than widen the page */
.informal-stop-why { font-size: 12px; color: var(--ink-3); max-width: 70ch;
                     overflow-wrap: anywhere; }
.informal-absent { font-size: 12.5px; color: var(--ink-2); overflow-wrap: anywhere; }

@media (max-width: 720px) {
  /* the action row's note cannot share a phone line with its button */
  .informal-act { display: block; }
  .informal-act .btn { width: 100%; margin-bottom: 4px; }
  .informal-note { display: block; }
}

/* ------------------------------------------- what the client has been told (E6.1)

   The composed message is shown as it would arrive — wrapped, never clipped. It is prose a
   client reads, so it keeps its own line breaks (`pre-wrap`) and is capped at a readable measure
   rather than stretched across the panel. */
.notif-panel { margin-top: var(--s5, var(--s4)); }
.notif-panel h3 { margin: 0 0 var(--s2); font-size: 11px; text-transform: uppercase;
                  letter-spacing: 0.1em; color: var(--ink-3); font-weight: 680; }
.notif-head { margin: var(--s4) 0 var(--s2); font-size: 11px; text-transform: uppercase;
              letter-spacing: 0.09em; color: var(--ink-3); font-weight: 660; }
.notif-channel { margin-top: var(--s2); }
.notif-when { margin-top: 2px; }
.notif-text { margin: var(--s2) 0 0; font-family: var(--sans, inherit); font-size: 13px;
              line-height: 1.55; color: var(--ink); white-space: pre-wrap;
              overflow-wrap: anywhere; max-width: 68ch; }
.notif-card.quiet { background: var(--sunken); }
.notif-hold { border-left: 3px solid var(--warn-line); padding-left: var(--s3);
              color: var(--ink-2); }  /* token, not a literal amber outside the palette */
.notif-acts { margin-top: var(--s3); display: flex; flex-wrap: wrap; gap: var(--s2); }

@media (max-width: 720px) {
  /* one column, and the send control full width: it is the operator's action, not a decoration */
  .notif-acts .btn { width: 100%; }
  .notif-text { font-size: 13px; }
}

/* ---------------------------------------- which attempts print (§3.2, E3.4) — appended at the end
 * Kept at the END of the stylesheet on purpose: a block placed above a later rule that touches the
 * same element loses silently, and this one deliberately overrides nothing. It is drawn INSIDE
 * `.fmt-box` because the rule belongs to the format that owns it.
 *
 * The panel carries NO CONTROL — a toggle here is the per-job setting §3.2 forbids — so there is
 * no button rule and no interactive state to style. What it does carry is a warning when the rule
 * is not settled, which must not read as a settled decision.
 */
.ap-box { margin-top: var(--s3); border-top: 1px solid var(--line); padding-top: var(--s2); }
.ap-head { margin: 0 0 6px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
           color: var(--ink-3); font-weight: 680; }
.ap-quiet { color: var(--ink-4); }
.ap-warn { border-left: 3px solid var(--warn-line); padding-left: var(--s3); }
.ap-warn b { color: var(--warn); }
.ap-bad { border-left: 3px solid var(--danger-line); padding-left: var(--s3); }
.ap-bad b { color: var(--danger); }
.ap-list { margin: var(--s1) 0 0; }
.ap-rows { margin: var(--s2) 0 0; padding-left: 0; list-style: none; counter-reset: ap; }
.ap-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2);
          padding: var(--s2) 0; border-top: 1px solid var(--line); }
.ap-row:first-child { border-top: 0; }
.ap-line { flex: 1 1 22ch; min-width: 0; font-size: 13px; line-height: 1.5; color: var(--ink);
           overflow-wrap: anywhere; }
/* the record the line was built from: R2, the value's provenance one step from the value */
.ap-trace { flex: 0 1 auto; font-size: 11px; color: var(--ink-4); overflow-wrap: anywhere; }

@media (max-width: 720px) {
  /* one line per attempt, the trace under it: a long method text and an id on one row at 390px
     leaves the line ~90px and breaks a word in half */
  .ap-row { display: block; }
  .ap-trace { display: block; margin-top: 2px; }
}

/* ====================================================== photos on an attempt (E3.3)

 * A photo documents ONE attempt, so everything here renders INSIDE that attempt's reading row
 * (`.att-read`), never at job level. Three things have to stay readable together:
 *
 *   * the image itself, openable in one click from the record it hangs off (R2);
 *   * the photo's OWN time and the file's own EXIF time, drawn as two separate readings — they
 *     are different facts and the page must not merge them into one;
 *   * the conflicts, which are flags and not decisions: when the record and the file disagree,
 *     both values stay on screen.
 *
 * `min-width: 0` and `overflow-wrap: anywhere` on the row are not decoration: a case-relative
 * path inside a grid track sets a min-content floor that widens the whole page on a phone (the
 * trap `.req-card` hit at 457px inside a 390px viewport).
 */
.att-photos { margin: var(--s1) 0 0; padding-left: 0; list-style: none;
              display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s2); }
.att-photo { min-width: 0; border: 1px solid var(--line); border-radius: var(--r-sm);
             padding: var(--s2); background: var(--raised); }
.att-photo-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
.att-photo-head .mono { min-width: 0; overflow-wrap: anywhere; }
.att-photo-img { display: block; margin-top: 6px; }
.att-photo-img img { display: block; max-width: 100%; height: auto; border-radius: var(--r-sm);
                     border: 1px solid var(--line-2); }
.att-photo-cap { margin-top: 4px; font-size: 12px; color: var(--ink-2); }
.att-photo-missing { margin-top: 6px; font-size: 12px; color: var(--warn);
                     border-left: 3px solid var(--warn-line); padding-left: var(--s2); }
.att-photo .att-quiet { font-size: 11px; overflow-wrap: anywhere; }

/* A disagreement between the record and the file: a FLAG, and it says which two values disagree.
   It is not styled as an error the operator must clear — it is a reading the page will not
   resolve on its own, exactly like a contradictory source on a field. */
.att-conflict { margin-top: 4px; font-size: 12px; color: var(--danger);
                border-left: 3px solid var(--danger-line); padding-left: var(--s2); }
.att-conflict .mono { display: block; color: var(--ink-3); font-size: 11px;
                      overflow-wrap: anywhere; }

/* The exhibit page (E4.6): the photo, and the ATTEMPT's own date and time beside it. The grid is
   minmax(0, 1fr) for the same reason as above. */
.att-exhibit { margin-top: var(--s3); }
.att-exhibit-grid { display: grid; grid-template-columns: minmax(0, 160px) minmax(0, 1fr);
                    gap: var(--s3); align-items: start; }
.att-exhibit-grid img { display: block; max-width: 100%; height: auto; border-radius: var(--r-sm);
                        border: 1px solid var(--line-2); }
.att-not-settled { border-left: 3px solid var(--warn-line); }
.att-not-settled .att-why { border-top: 0; }

@media (max-width: 720px) {
  /* one column: a 160px thumbnail beside a label at 390px leaves the label ~180px and breaks the
     attempt id mid-word */
  .att-exhibit-grid { grid-template-columns: minmax(0, 1fr); }
  .att-photo-head { display: block; }
  .att-photo-head .mono { display: block; }
}

/* The append-only verdict (E7.2). The panel only appears when a record store is not what it was
   appended as, or is not covered yet, so it reads as an alarm rather than a fixture. `minmax(0,
   1fr)` on the track and `overflow-wrap: anywhere` on the problem text, because these lines carry
   file names and hashes and a long one must not set a min-content floor that widens a phone page. */
.ao-panel { border-left: 3px solid var(--warn-line); }
.ao-panel h3.ao-broken { color: var(--danger); }
.ao-panel .ao-store { border-left: 3px solid var(--warn-line); }
.ao-panel .ao-store-broken { border-left-color: var(--danger); }
.ao-panel .ao-problems { margin: var(--s2) 0 0; padding-left: 1.1em;
                         list-style: disc; }
.ao-panel .ao-problems li { font-size: 12px; color: var(--ink-3);
                            overflow-wrap: anywhere; }
.ao-panel .ao-store h4 .mono { display: block; }

/* ====================================================== merge-field mapping (E4.3)
   The panel draws the SERVER's map: which of the job's values goes on which blank of the office's
   own supplied PDF, WHY that blank was chosen, and every blank it could have been with its own
   reasons. Nothing here is computed in CSS — the classes only say what state a row is in.

   `minmax(0, 1fr)` on the tracks and `overflow-wrap: anywhere` on the value and the reasons,
   because these lines carry addresses, quotes and file names: a long one must not set a
   min-content floor that widens the page at phone width. */
.map-box { border-left: 3px solid var(--accent-line); }
.map-box .map-quiet { color: var(--ink-3); }
.map-box .map-warn { color: var(--warn); }
.map-box .map-null { color: var(--ink-3); font-style: italic; }
.map-sec { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s2); }
.map-unmapped > summary { cursor: pointer; color: var(--ink-2); font-size: 13px;
                          padding: var(--s2) 0; }
.map-withheld > summary { cursor: pointer; color: var(--ink-2); font-size: 13px;
                          padding: var(--s2) 0; }
.map-card { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s1);
            margin: 0 0 var(--s2); }
.map-card.off { /* off is a state, not a severity: no bar */ }
.map-card.on { border-left: 3px solid var(--ok-line); }
.map-head { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline; }
.map-label { font-weight: 600; color: var(--ink); }
.map-key { color: var(--ink-3); font-size: 11px; }
.map-chip { font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
            padding: 1px 6px; border-radius: var(--r-pill);
            background: var(--ok-soft); color: var(--ok); border: 1px solid var(--ok-line); }
.map-chip.pinned { background: var(--accent-soft); color: var(--accent);
                   border-color: var(--accent-line); }
.map-chip.off { background: transparent; color: var(--ink-3); border-color: var(--line-2); }
.map-line { display: block; font-size: 12px; color: var(--ink-2); }
.map-k { display: inline-block; min-width: 3.6em; color: var(--ink-3);
         text-transform: uppercase; letter-spacing: .04em; font-size: 10px; }
.map-val, .map-blank, .map-src, .map-via { overflow-wrap: anywhere; }
.map-val { color: var(--ink); }
.map-blank { color: var(--ink-2); }
.map-via, .map-src { color: var(--ink-3); }
.map-why { margin: var(--s1) 0 0; padding-left: 1.1em; list-style: disc; }
.map-why li { font-size: 12px; color: var(--ink-2); overflow-wrap: anywhere; }
.map-reason { margin: var(--s1) 0 0; font-size: 12px; color: var(--ink-2);
              overflow-wrap: anywhere; }
.map-alts > summary { cursor: pointer; font-size: 11px; color: var(--ink-3);
                      padding: 2px 0; }
.map-alt-list { list-style: none; margin: var(--s1) 0 0; padding: 0;
                display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s2); }
.map-alt { padding-left: var(--s2); font-size: 12px; }  /* a secondary reading, no bar needed */
.map-alt-blank { display: block; color: var(--ink-2); overflow-wrap: anywhere; }
.map-alt-score { display: inline-block; color: var(--ink-3); font-size: 11px; }
.map-alt-text { display: block; color: var(--ink-2); overflow-wrap: anywhere; }
.map-alt-why { margin: 2px 0 var(--s1); padding-left: 1.1em; list-style: disc;
               color: var(--ink-3); font-size: 11px; }
.map-alt-why li { overflow-wrap: anywhere; }
.map-conflicts li { overflow-wrap: anywhere; }
.map-stored { font-size: 11px; }
.map-files { list-style: none; margin: var(--s2) 0; padding: 0;
             display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s2); }
.map-file { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline; }
.map-fname { color: var(--ink); overflow-wrap: anywhere; }
.map-fmeta { color: var(--ink-3); font-size: 11px; }
.map-fbad { color: var(--danger); font-size: 11px; overflow-wrap: anywhere; }
.map-fpicks { display: flex; flex-wrap: wrap; gap: var(--s2); margin: var(--s2) 0; }
.map-fpick { font-size: 11px; padding: 2px 8px; border: 1px solid var(--line-2);
             border-radius: var(--r-pill); background: transparent; color: var(--ink-2);
             cursor: pointer; }
.map-fpick.on { background: var(--accent-soft); border-color: var(--accent-line);
                color: var(--accent); }
.map-row { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); }

@media (max-width: 720px) {
  /* The heading line carries a label, the field's own key and a state chip; at 390px they stack
     rather than squeeze. The action row wraps for the same reason — three buttons on one line at
     phone width is a row of 60px targets. */
  .map-head { display: block; }
  .map-head .map-key { display: block; }
  .map-row .btn { flex: 1 1 auto; }
  .map-line { display: block; }
  .map-k { display: block; min-width: 0; }
}

/* ============================================ the affidavit of service (E4.2)

   The panel is a document-shaped surface: a grid of fields, each with its value, its source, the
   machine's own reading beside a correction, and a stated reason when it is blank. The long value
   is a paragraph, so every box here carries `overflow-wrap: anywhere` — a field row whose value is
   a 200-character address sets a min-content floor and widens the whole phone page, which has
   happened twice on this console (the destination-requirements card at 457px inside a 390px
   viewport). The grid track is `minmax(0, 1fr)` for the same reason: `1fr` alone cannot shrink
   below its content's min-content width. */

.aff-panel { margin: var(--s4) 0 0; }
.aff-panel h3 { margin: 0 0 var(--s2); font-size: 11px; text-transform: uppercase;
                letter-spacing: 0.1em; color: var(--ink-3); font-weight: 680; }
.aff-head { margin: var(--s5) 0 var(--s2); font-size: 12px; text-transform: uppercase;
            letter-spacing: 0.07em; color: var(--ink-3); font-weight: 680; }
.aff-quiet { color: var(--ink-3); }
.aff-bad { color: var(--danger); }
.aff-bad-card { border-color: var(--danger, var(--line)); }

.aff-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
            gap: var(--s3); }
.aff-field { border: 1px solid var(--line); border-radius: var(--r-md); background: var(--raised);
             padding: var(--s3) var(--s4); min-width: 0; overflow-wrap: anywhere; }
.aff-field.aff-blank { border-style: dashed; }
.aff-field.aff-edited { border-color: var(--accent-line); }
.aff-field-head { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline; }
.aff-field-label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.07em;
                   color: var(--ink-3); font-weight: 680; }
.aff-flag { font-size: 10px; padding: 1px 7px; border-radius: var(--r-pill);
            background: var(--accent-soft); border: 1px solid var(--accent-line);
            color: var(--accent); white-space: nowrap; }
.aff-flag-quiet { background: transparent; border-color: var(--line-2); color: var(--ink-3); }
.aff-value { margin: 6px 0 0; font-size: 13px; color: var(--ink); white-space: pre-wrap;
             overflow-wrap: anywhere; }
.aff-system { color: var(--ink-3); font-size: 11px; margin: 4px 0 0; overflow-wrap: anywhere; }
.aff-src { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline;
           margin: var(--s2) 0 0; }
.aff-src .pset-note { margin: 0; overflow-wrap: anywhere; }
.aff-switch { margin: var(--s2) 0 0; overflow-wrap: anywhere; }
.aff-switch[data-notary="off"] { color: var(--ink-3); }
.aff-switch[data-notary="on"] { color: var(--ink); }
.aff-edit { display: flex; flex-wrap: wrap; gap: var(--s2); margin: var(--s2) 0 0; }
.aff-edit .aff-input, .aff-edit .aff-instruct { flex: 1 1 100%; min-width: 0; }
.aff-actions { margin: var(--s2) 0 0; }
.aff-row { display: flex; flex-wrap: wrap; gap: var(--s3); align-items: baseline;
           margin: var(--s3) 0 0; }

/* The composed page, exactly as the module wrote it — a monospace block so the office's own line
   breaks are the ones the document has. */
.aff-document { margin: var(--s2) 0 0; padding: var(--s4); border: 1px solid var(--line);
                border-radius: var(--r-md); background: var(--raised); color: var(--ink);
                font-family: var(--mono); font-size: 12px; line-height: 1.7;
                white-space: pre-wrap; overflow-wrap: anywhere; }

.aff-gaps, .aff-blanks .notes { margin: var(--s2) 0 0; padding-left: 1.1em; list-style: disc; }
.aff-gap { font-size: 12px; color: var(--ink-2); margin: 0 0 6px; overflow-wrap: anywhere; }
.aff-gap b { color: var(--ink); }
.aff-gap-quiet { color: var(--ink-3); }
.aff-gap-closed { color: var(--ink-3); }
.aff-closed { margin: var(--s3) 0 0; }
.aff-closed summary { cursor: pointer; font-size: 12px; color: var(--ink-2);
                      list-style: none; }
.aff-closed summary::-webkit-details-marker { display: none; }
.aff-closed summary .caret { display: inline-block; margin-left: 6px; color: var(--ink-3);
                             transition: transform 0.15s; }
.aff-closed[open] summary .caret { transform: rotate(90deg); }

/* A FIELD THE DOCUMENTS DISAGREE ABOUT (R3, E4.4). The flag is the point: without it a contested
   field renders exactly like one the documents agree about, and the review screen is the one place
   a person is meant to settle it. The other readings are OPEN by default — a <details> that starts
   closed hides exactly the text the operator has to read, which is the trap the mapping panel
   already carries a note about. */
.aff-flag-flag { background: var(--warn-bg, rgba(180, 120, 20, 0.14));
                 border-color: var(--warn-line, var(--accent-line)); color: var(--ink); }
.aff-field.aff-contested { border-color: var(--accent-line); }
.aff-contest { margin: var(--s2) 0 0; }
.aff-contest summary { cursor: pointer; font-size: 11px; color: var(--ink-2); list-style: none; }
.aff-contest summary::-webkit-details-marker { display: none; }
.aff-contest summary .caret { display: inline-block; margin-left: 6px; color: var(--ink-3);
                              transition: transform 0.15s; }
.aff-contest[open] summary .caret { transform: rotate(90deg); }
.aff-others { margin: var(--s2) 0 0; padding-left: 0; list-style: none; }
.aff-other { font-size: 12px; color: var(--ink-2); margin: 0 0 var(--s2);
             padding-left: var(--s3); border-left: 2px solid var(--line-2);
             overflow-wrap: anywhere; }
.aff-other .cand-val { color: var(--ink); }
.aff-other-src { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline;
                 margin-top: 3px; }
/* AN EDIT THE FORMAT NO LONGER OPENS (R4/R8). It is drawn, not swallowed: the value above is the
   reading, and this says why the operator's correction is not the one in force. */
.aff-stale { margin: var(--s2) 0 0; color: var(--danger, var(--ink-2));
             overflow-wrap: anywhere; }

/* EVERY CHANGE TO THE AFFIDAVIT (E4.10). The log is drawn open, one card per record, in creation
   order — the order IS the story, so the cards are listed rather than gridded, and each one wraps
   its own value and time (a long instruction or a long value must not widen the panel; measured on
   the mapping panel, where a long address stretched the page). */
.aff-log-panel { display: flex; flex-direction: column; }
.aff-log-panel .aff-changes { display: flex; flex-direction: column; gap: var(--s2); }
.aff-log-panel .aff-change { padding: var(--s2) var(--s3); }
.aff-log-panel .aff-change h4 { margin: 0 0 2px; font-size: 11px; }
.aff-log-panel .aff-change h4 .mono { display: inline; margin-left: var(--s2); }
.aff-log-panel .aff-change p { margin: 0; overflow-wrap: anywhere; }
/* `.mono` is `display: block` GLOBALLY (a value's source line gets its own row), so a timestamp
   wrapped in it inside a sentence is torn onto a line of its own. That is the defect measured on
   the desk capture while every field-level assertion passed. A date is a value inside this
   sentence, not a source line: unblock it and let it wrap like the words around it.
   (NOTE: `.pset-note` is NOT `white-space: pre-wrap` — it computes `normal` on the rendered panel,
   so a template literal broken across source lines COLLAPSES and does not reach the screen. The
   classes that DO preserve a source wrap are `.req-card p`, `.reason`, `.act-body`, `.aff-value`.
   Check `getComputedStyle` rather than a class's own rule: `white-space` is inherited.) */
.aff-log-panel .aff-change .aff-when { display: inline; font-family: var(--mono);
                                       font-size: 11px; }
.aff-log-panel .aff-change .aff-instruction,
.aff-log-panel .aff-change .aff-supersedes { margin-top: 2px; }
/* A RECORD THAT PREDATES THE LOG is marked, not hidden: it is a real gap in the attribution, and a
   reader who took the assembly's own `generated_by` for the change's would be told the wrong
   person made it (R1). */
.aff-log-panel .aff-change-unstated { border-left: 3px solid var(--warn-line); }
.aff-log-panel .aff-change .aff-unstated { margin-top: 2px; }

@media (max-width: 720px) {
  /* One column: a 390px phone cannot hold two value boxes side by side, and the value is the
     thing being read. The heading line stacks rather than squeezing the label against its chip. */
  .aff-grid { grid-template-columns: minmax(0, 1fr); }
  .aff-field-head { display: block; }
  .aff-field-head .aff-flag { display: inline-block; margin-top: 4px; }
  .aff-row .btn { flex: 1 1 auto; }
  .aff-document { padding: var(--s3); font-size: 11px; }
}
.map-noteclear { margin: var(--s1) 0 0; font-size: 11px; color: var(--ink-3);
                 overflow-wrap: anywhere; }
/* An action that belongs to the blank line, grouped in its own element so the source template's
   indentation cannot bleed into the rendered line — `.mono` is `display: block` (a global rule for
   a value's source line), so the whitespace between two interpolations rendered as a stray indent
   in front of the button. */
.map-actions { display: inline-block; margin-left: var(--s2); }
@media (max-width: 720px) {
  .map-actions { display: block; margin: var(--s2) 0 0; }
}

/* THE EXHIBIT PAGE ON THE AFFIDAVIT (E4.6). Appended at the END, after every phone override, for
   the reason the sibling `.att-exhibit` block learned the hard way: a rule added earlier is
   silently overridden by a later media query, and a phone layout that "did not take" reads as a
   broken grid rather than a stylesheet order problem.

   The grid is `minmax(0, 1fr)` on the text column, not `1fr`: a `1fr` track has an automatic
   minimum (its content) and a long attempt id or file path then widens the page instead of
   wrapping inside its column. */
.aff-exhibit { margin-top: var(--s3); }
.aff-exhibit-card { min-width: 0; }
.aff-exhibit-grid { display: grid; grid-template-columns: minmax(0, 170px) minmax(0, 1fr);
                    gap: var(--s3); align-items: start; min-width: 0; }
.aff-exhibit-grid img { display: block; max-width: 100%; height: auto; border-radius: var(--r-sm);
                        border: 1px solid var(--line-2); }
/* The photo's bytes are read at read time, so a photo whose file is gone is drawn as its own
   state rather than as a broken image glyph. */
.aff-exhibit-missing { padding: var(--s3); border: 1px dashed var(--warn-line, var(--line-2));
                       border-radius: var(--r-sm); font-size: 12px; color: var(--warn, var(--ink-2)); }
/* The label is the point of the block: the ATTEMPT's own date and time. It is given the accent so
   it cannot be mistaken for the assembled-document date printed elsewhere on the record. */
.aff-exhibit-label { font-size: 13px; }
.aff-exhibit-grid .att-quiet { font-size: 11px; overflow-wrap: anywhere; }
.aff-exhibit-cap { margin: var(--s2) 0 0; font-size: 12px; color: var(--ink-2);
                   overflow-wrap: anywhere; }
.aff-exhibit-why { margin: var(--s1) 0 0; font-size: 12px; }
/* NOT SETTLED (two served attempts, or several unmarked photos): a flag, not an error to clear —
   the same treatment `.att-not-settled` gets on the attempt panel. */
.aff-exhibit-settled-no { border-left: 3px solid var(--warn-line, var(--accent-line)); }

@media (max-width: 720px) {
  /* one column: a 170px thumbnail beside a label at 390px leaves the label ~180px, and the
     attempt id and the file path both break mid-word. */
  .aff-exhibit-grid { grid-template-columns: minmax(0, 1fr); }
}

/* A not-settled candidate row (E4.6). `.mono` is `display: block` globally — that is right for a
   value's source line under a field and wrong inside a sentence, where it breaks the row and hangs
   the em-dash at the start of the next line. The row is one line by construction; this only says so. */
.aff-exhibit-cand { overflow-wrap: anywhere; }
.aff-exhibit-cand .mono { display: inline; }

/* APPROVE AND SAVE, OR REJECT (E4.11). The panel is where the decision is taken, and the task a
   rejection leaves open has to be readable at 390px: every row wraps inside its own column, and no
   row relies on a `1fr` track (whose automatic minimum is its content, and a long field label then
   widens the page rather than wrapping — the trap the exhibit grid above already records). */
.rev-panel { margin: var(--s4) 0 0; }
.rev-actions { display: flex; flex-wrap: wrap; gap: var(--s2); margin: var(--s3) 0 0; }
.rev-reject { margin: var(--s3) 0 0; padding: var(--s3); border: 1px solid var(--line-2);
              border-radius: var(--r-sm); }
.rev-reject .rev-row { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center;
                       margin: 0 0 var(--s2); }
.rev-reject .rev-row label { font-size: 11px; color: var(--ink-3); }
.rev-reject .rev-row select,
.rev-reject .rev-row input { min-width: 0; flex: 1 1 200px; }
.rev-pending { margin: var(--s2) 0; padding-left: 0; list-style: none; }
.rev-pending-item { font-size: 12px; color: var(--ink-2); margin: 0 0 4px;
                    overflow-wrap: anywhere; }
/* `.notes` IS A PROSE LIST AND CARRIES A MARKER. `.notes li::before` draws an absolutely-positioned
   chevron at `left: 0, top: 13px`, and it is more specific than any single-class rule here — so
   borrowing the class (`class="notes rev-task"`) drew the app's chevron AND this list's own disc on
   every row, and `content: none` could not turn it off. These two lists therefore do NOT carry the
   class: they are their own list, with no marker at all, the same as `.rev-pending` above them.
   Measured on the running page with `getComputedStyle(li, '::before')`, not inferred — the probe in
   `tools/verify_review_decide.py` reads exactly this and its negative control adds a marker back. */
.rev-items, .rev-task { margin: var(--s2) 0 0; padding: 0; list-style: none; }
.rev-item, .rev-task-item { margin: 0 0 8px; padding: 0; font-size: 12px;
                            color: var(--ink-2); overflow-wrap: anywhere; }
.rev-item b, .rev-task-item b { color: var(--ink); }
/* `.mono` is `display: block` globally (a value's source line gets its own row), so a field name
   wrapped in it INSIDE a row is torn onto a line of its own — the same defect the mapping and
   affidavit panels each carry a note about. */
.rev-item .mono, .rev-task-how .mono { display: inline; }
.rev-task-how { display: block; margin-top: 2px; font-size: 11px; color: var(--ink-3);
                overflow-wrap: anywhere; }
/* The "on the affidavit now" value gets its own line under "what to do", so a long value cannot
   read as part of the instruction. */
.rev-task-how span { display: block; }
.rev-run-card, .rev-open { border-left: 3px solid var(--warn-line, var(--accent-line)); }
.rev-open h4 { margin: 0 0 var(--s2); }
/* The open task is the thing the operator came back to the job to read: it is not a quiet aside. */
.rev-open .rev-task-item { color: var(--ink-2); }
.rev-stale { margin: var(--s2) 0 0; color: var(--danger, var(--ink-2));
             overflow-wrap: anywhere; }
/* A recorded decision reads as a stamp, the same shape the approval note uses — one sentence in
   ONE string, because `.pset-note` is `pre-wrap` and a template literal broken across source lines
   renders the source's own wrap and hanging indent as content. */
.rev-panel #rev-decision, .rev-panel [id$="-decision"] { overflow-wrap: anywhere; }

/* THE INVOICE (E5). An invoice raised against a completed job, derived from it, approved then sent.

   Two layout facts drive every rule here, and both are this console's own measured traps:

     * `.mono` is `display: block` GLOBALLY, so a value's source line gets its own row under a field.
       Inside an invoice LINE — "1 × Service of process — AUD 385.00" — that would tear the line into
       four stacked fragments with the money floating away from what it is for. Every `.mono` inside
       `.inv-line` and `.inv-money` rows is therefore put back to `display: inline`.
     * a `1fr` track's automatic minimum is its CONTENT, so a long document path or a long
       description widens the panel (and the page) rather than wrapping. Rows are flex-wrap with
       `min-width: 0`, and every cell wraps anywhere. */
.inv-panel { margin: var(--s4) 0 0; }
.inv-panel .inv-meaning { overflow-wrap: anywhere; }
.inv-lines { margin: var(--s2) 0 0; padding: 0; list-style: none; }
.inv-line { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline;
            margin: 0 0 6px; padding: 0; font-size: 12px; color: var(--ink-2);
            overflow-wrap: anywhere; }
.inv-line > * { min-width: 0; }
/* the money is the point of the row: it is the ink, not a quiet aside */
.inv-line .inv-what { flex: 1 1 200px; color: var(--ink); }
.inv-line .inv-money { color: var(--ink); font-weight: 600; white-space: nowrap; }
.inv-line .inv-qty { color: var(--ink-3); }
.inv-line .inv-src { flex: 1 1 100%; font-size: 11px; color: var(--ink-3); }
/* `.mono` is block by default — inside these rows it must not be. */
.inv-line .mono, .inv-panel .inv-approved .mono, .inv-panel .inv-sending .mono { display: inline; }
.inv-total { margin: var(--s2) 0 0; font-size: 13px; overflow-wrap: anywhere; }
.inv-total b { color: var(--ink); }
.inv-approved, .inv-sending, .inv-void, .inv-drift li { overflow-wrap: anywhere; }
/* A delivery on an EARLIER version is the fact a person has to read before pressing anything: it
   gets the same raised edge as a voided approval for the same reason — it is not a quiet aside. */
.inv-prev, .inv-redel { border-left: 3px solid var(--warn-line, var(--accent-line)); padding-left: var(--s2); }
/* A voided approval is the R6 consequence and it is NOT a quiet aside: it is the reason the invoice
   is in front of a person again. */
.inv-void { border-left: 3px solid var(--warn-line, var(--accent-line)); padding-left: var(--s2); }
.inv-sent-chip { color: var(--ink); }
.inv-acts { display: flex; flex-wrap: wrap; gap: var(--s2); margin: var(--s3) 0 0; }
/* The author, the price and the instruction are FIELDS beside the controls, not dialogs: an
   operator has to be able to see what the act will carry while they fill it in, and a modal blocks
   the page (measured — a headless run hung on it). The author field is required by the module and
   is given the width to say so. */
.inv-acts .inv-by, .inv-acts .inv-amount, .inv-acts .inv-instruction { min-width: 0; font: inherit; }
.inv-acts .inv-by { flex: 1 1 220px; }
.inv-acts .inv-price { flex: 1 1 240px; min-width: 0; }
.inv-acts .inv-amount { flex: 1 1 150px; }
.inv-acts .inv-instruction { flex: 2 1 260px; }
.inv-panel .st-gaps li, .inv-panel .inv-gaps li { overflow-wrap: anywhere; }

@media (max-width: 720px) {
  /* At 390px the row wraps. The FIRST version of this stacked every cell and the quantity was left
     alone on its own line — "1 ×" with the description under it — which reads as an orphan. MEASURED
     by reading the capture. On a phone the line is its OWN GRID instead: the description takes the
     free width, the quantity stops wrapping away from it, and the money sits on the right of the
     first row where it can be read against what it is for. `minmax(0, …)` because a grid track's
     automatic minimum is its CONTENT and a long description would otherwise widen the page. */
  .inv-line { display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
              align-items: baseline; column-gap: var(--s2); row-gap: 2px; }
  .inv-line .inv-qty { grid-column: 1; }
  .inv-line .inv-what { grid-column: 2; min-width: 0; }
  .inv-line .inv-money { grid-column: 3; white-space: nowrap; }
  .inv-line .inv-src { grid-column: 1 / -1; }
  .inv-acts { display: block; }
  .inv-acts .btn { display: block; width: 100%; margin: 0 0 var(--s2); }
  /* A `<select>`'s automatic minimum is its CONTENT, so the price picker's longest option
     ("SERVICE_INTL — Service of process — international — AUD 620.50") widened the page to 585px on
     a 390px phone and the panel scrolled sideways. MEASURED, not inferred: the width audit above
     reported `.inv-price` at right=585. `width: 100%` plus `max-width: 100%` is what bounds it —
     `min-width: 0` alone does not, because the select's own intrinsic width wins against a flex
     basis it is no longer participating in under `display: block`. */
  .inv-acts .inv-by, .inv-acts .inv-price, .inv-acts .inv-amount,
  .inv-acts .inv-instruction { display: block; width: 100%; max-width: 100%; min-width: 0;
                               box-sizing: border-box; margin: 0 0 var(--s2); }
}

/* THE HELD SIGNATURE (E4.5). "Held with the process server's permission, applied on the operator's
   instruction, or the affidavit is sent for signature instead."

   The same two measured traps as the invoice panel govern every rule here:

     * `.mono` is `display: block` GLOBALLY, so a `<span class="mono">` inside a `set-line` — the
       record's file name, a when, the appearance — would be torn onto its own row and read as a
       fragment rather than as part of the sentence it belongs to. Every `.mono` inside a `.sig-*`
       line is put back to `display: inline`.
     * a `1fr` track's automatic minimum is its CONTENT, so a long permission line or a long
       instruction widens the panel (and the page) rather than wrapping. The two-field row is
       flex-wrap with `min-width: 0`, and every line wraps anywhere. */
.sig-panel { margin: var(--s4) 0 0; }
.sig-panel .sig-state { font-weight: 600; letter-spacing: .04em; }
.sig-held, .sig-applied, .sig-none, .sig-acts, .sig-act, .sig-withdraw, .sig-apply-act {
  overflow-wrap: anywhere;
}
.sig-held { border-left: 3px solid var(--accent-line); }
/* An application that no longer stands over this document is the R6 consequence, and it is NOT a
   quiet aside: it is the reason the affidavit in front of the operator is not the signed one. */
.sig-stale { border-left: 3px solid var(--warn-line, var(--accent-line)); }
.sig-stale-why { color: var(--danger, var(--ink-2)); }
.sig-held .set-line, .sig-applied .set-line { margin: 2px 0; font-size: 12px; color: var(--ink-2); }
.sig-k { color: var(--ink-3); font-size: 11px; }
.sig-words { font-style: italic; }
.sig-label { display: block; font-size: 11px; color: var(--ink-3); margin: var(--s2) 0 2px; }
.sig-server, .sig-panel input[type="text"], .sig-panel select {
  display: block; width: 100%; min-width: 0; max-width: 100%;
}
/* Two fields side by side on the desk; stacked at the phone width below, where a 200px basis would
   overflow a 390px screen once the labels and the panel padding are counted. */
.sig-row { display: flex; flex-wrap: wrap; gap: var(--s2); }
.sig-row > div { flex: 1 1 200px; min-width: 0; }
.sig-act .btn, .sig-withdraw .btn { margin: var(--s3) 0 0; }
/* `.mono` is block by default — inside these lines it must not be. Measured the same way the
   invoice and mapping panels were: a `set-line` whose value was torn onto its own row reads as a
   fragment and the sentence loses its meaning. */
.sig-held .mono, .sig-applied .mono, .sig-panel .set-line .mono, .sig-panel .pset-note .mono {
  display: inline;
}
/* The trail's own list: a record's file name is one token, and it gets its own row. */
.sig-trail { margin: var(--s3) 0 0; }
.sig-trail summary { cursor: pointer; font-size: 12px; color: var(--ink-2); }
.sig-bad li { color: var(--danger, var(--ink-2)); }

@media (max-width: 720px) {
  .sig-row { display: block; }
  .sig-row > div { margin: 0 0 var(--s2); }
  .sig-act .btn, .sig-withdraw .btn { display: block; width: 100%; }
}

/* MY SERVERS' OWN HEADER, at the widths where its own labelled text tips a rule every overlay
   shares. `#servers-overlay` puts a title, `#srv-scope` (the caller label, as long as a client
   id) and TWO controls into `.form-overlay-head`, which is `display:flex; flex-wrap:nowrap` —
   so once the content is wider than the bar it goes through `overflow: visible`, the document
   reports NO overflow at all, and the Close control is simply pushed past the right edge. That
   is the one control that always has to be reachable: measured at 360px with a realistic client
   id, Close was 0/66px visible and a real click at its visible centre did not close the sheet.
   Scoped to `#servers-overlay`: the sibling overlays that share `.form-overlay-head` (the USM94
   form, Notifications) lay out correctly and this card's text is what tips the shared rule, so
   this card carries its own fix rather than re-laying out every overlay. */
@media (max-width: 720px) {
  #servers-overlay .form-overlay-head { flex-wrap: wrap; }
  /* the title takes the first row and may shrink; `.fo-title` is `display:flex`, so the label
     inside needs `min-width: 0` before it can truncate at all */
  #servers-overlay .fo-title { flex: 1 1 auto; min-width: 0; }
  #servers-overlay .fo-title .mono {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
  }
  /* the controls never shrink and never wrap, and they take their own row when the title has
     used the first one — so Close is on screen at every width, including 320px (their 40px
     touch height is already set by the overlay block above; nothing to repeat here) */
  #servers-overlay .fo-actions { flex: none; }
}

/* THE PAYMENT STATUS (E5.2). Its own block, appended rather than folded into `.inv-*`: the two
   panels sit next to each other on an informal card and a shared rule would make a change to one
   move the other. `max-width: 100%` on the select is load-bearing — a `<select>`'s automatic
   minimum width is its longest OPTION, and the invoice panel measured a 390px phone page widened to
   585px by exactly that. This picker's options are status labels, so it would do the same. */
.pay-panel { margin: var(--s4) 0 0; }
.pay-panel .pay-meaning, .pay-panel .pay-src, .pay-panel .pay-instr, .pay-panel .pay-when,
.pay-panel .pay-amt, .pay-panel .pay-ref, .pay-panel .pay-prov, .pay-panel .pay-approval,
.pay-panel .pay-gaps li { overflow-wrap: anywhere; }
.pay-acts { display: flex; flex-wrap: wrap; gap: var(--s2); margin: var(--s3) 0 0; }
.pay-acts .pay-status { width: 100%; max-width: 100%; min-width: 0; font: inherit; flex: 1 1 100%; }
.pay-acts .pay-by { flex: 1 1 220px; min-width: 0; font: inherit; }
.pay-acts .pay-paid-on { flex: 1 1 200px; min-width: 0; font: inherit; }
.pay-acts .pay-amount { flex: 0 1 140px; min-width: 0; font: inherit; }
.pay-acts .pay-currency { flex: 0 1 90px; min-width: 0; font: inherit; }
.pay-acts .pay-reference { flex: 1 1 180px; min-width: 0; font: inherit; }
.pay-acts .pay-instruction { flex: 2 1 260px; min-width: 0; font: inherit; }
.pay-history .st-move, .pay-vocab .notes li { overflow-wrap: anywhere; }

/* ==================================================================== THE NEW JOB FORM (intake)

   The screen the office types the job into. It is the ONLY producer of values on the informal
   workflow — no model runs there at all — so it is laid out for filling in rather than for
   reading: a two-column grid on the desk, one column on a phone. `min-width: 0` on the controls
   is load-bearing: a grid item's automatic minimum is its content, and a long option or a long
   case name otherwise widens the whole page instead of scrolling inside its own box. */
.intake-panel { margin: var(--s4) 0 0; }
.intake-groups { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s3) var(--s4); }
.intake-group { min-width: 0; }
.intake-group-head { margin: 0 0 var(--s1); font-size: 0.95em; }
.intake-caption { margin: 0 0 var(--s2); }
.intake-field { margin: 0 0 var(--s2); min-width: 0; }
.intake-label { display: block; font-size: 0.85em; color: var(--ink-3); margin: 0 0 2px; }
.intake-req { color: var(--warn, #a36b00); font-size: 0.9em; }
.intake-control { min-width: 0; }
.intake-input { width: 100%; max-width: 100%; min-width: 0; font: inherit;
                padding: 3px 6px; border: 1px solid var(--line-2); border-radius: 3px;
                background: var(--raised); color: var(--ink); }
.intake-input:focus-visible { outline: 2px solid var(--accent, #2f6f4f); outline-offset: 1px; }
.intake-gap { margin: 2px 0 0; font-size: 0.8em; overflow-wrap: anywhere; }
.intake-blank .intake-label { color: var(--ink-4); }
.intake-actions { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center;
                  margin: var(--s3) 0 0; }
.intake-quiet { flex: 1 1 240px; min-width: 0; }
.intake-result { margin: var(--s2) 0 0; overflow-wrap: anywhere; }
.intake-result-bad { color: var(--danger, #a33); }
@media (max-width: 760px) { .intake-groups { grid-template-columns: 1fr; } }

/* ============================================================ THE AFFIDAVIT DOCUMENT (the page)

   The document itself, as a list of parts. Each part is a row with its value, the source it
   travelled with, and a control that opens an editor in place — "like MS Word" in the sense that
   matters here: you change the words where they are, and the change is kept against that part.
   The value is set in the serif face and at document size, because it is the wording of a sworn
   page and reading it as UI chrome is what makes a mistake easy to miss. */
.affdoc-panel { margin: var(--s4) 0 0; }
.affdoc-page { border: 1px solid var(--line-2); border-radius: 3px; padding: var(--s3);
               background: var(--raised); }
.affdoc-run { padding: var(--s2) 0; border-bottom: 1px dashed var(--line); min-width: 0; }
.affdoc-run:last-child { border-bottom: 0; }
.affdoc-run-head { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline; }
.affdoc-run-label { font-size: 0.82em; color: var(--ink-3); text-transform: uppercase;
                    letter-spacing: .04em; }
.affdoc-when { margin: 0; font-size: 0.78em; }
.affdoc-value { font-family: 'Times New Roman', 'Liberation Serif', Georgia, serif;
                font-size: 1.02em; line-height: 1.35; margin: 3px 0 0;
                white-space: pre-wrap; overflow-wrap: anywhere; }
.affdoc-blank .affdoc-value { font-family: inherit; font-size: 0.9em; }
.affdoc-edited { border-left: 3px solid var(--accent, #2f6f4f); padding-left: var(--s2); }
.affdoc-system { margin: 3px 0 0; font-size: 0.8em; overflow-wrap: anywhere; }
.affdoc-actions { display: flex; flex-wrap: wrap; gap: var(--s2); margin: var(--s1) 0 0; }
.affdoc-editbox { display: flex; flex-wrap: wrap; gap: var(--s2); margin: var(--s2) 0 0;
                  min-width: 0; }
.affdoc-input { flex: 1 1 100%; width: 100%; max-width: 100%; min-width: 0; font: inherit;
                font-family: 'Times New Roman', 'Liberation Serif', Georgia, serif;
                padding: 4px 6px; border: 1px solid var(--line-2); border-radius: 3px;
                background: var(--bg); color: var(--ink); }
.affdoc-instruct { flex: 2 1 260px; min-width: 0; font: inherit; padding: 3px 6px;
                   border: 1px solid var(--line-2); border-radius: 3px; background: var(--bg);
                   color: var(--ink); }
.affdoc-notary { margin: var(--s2) 0; }
.affdoc-notary.off { color: var(--ink-3); }
.affdoc-problems { margin: var(--s2) 0; }
.affdoc-problems .notes { margin: 0; padding-left: 1.1em; }
.affdoc-problems .notes li { overflow-wrap: anywhere; }
.affdoc-actions-bar { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center;
                      margin: var(--s3) 0 0; }
.affdoc-quiet { flex: 1 1 260px; min-width: 0; }
.affdoc-result { margin: var(--s2) 0 0; overflow-wrap: anywhere; }
/* A collapsed group is a line; an open one is the work. `summary` is styled to read as a heading
   rather than as a disclosure widget, because the office should see their progress before they
   see anything else. */
.intake-group > summary { cursor: pointer; list-style: none; display: flex; gap: var(--s2);
                         align-items: baseline; flex-wrap: wrap; }
.intake-group > summary::-webkit-details-marker { display: none; }
.intake-group > summary::before { content: "▸"; color: var(--ink-4); font-size: 0.9em; }
.intake-group[open] > summary::before { content: "▾"; }
.intake-group-title { font-weight: 600; }
.intake-group-todo { color: var(--warn, #a36b00); font-size: 0.78em; }
/* The rendered page, as an IMAGE. PROJECT.md §9: "Rendered PNG pages, not an embedded PDF, for
   the form preview — an embedded viewer is a blank box in headless capture." The same rule
   applies to the affidavit, and for a second reason: the office has to SEE the page a process
   server will print, because a field-level check passes happily while the rendered page clips a
   long value. */
.affdoc-preview { margin: var(--s3) 0; }
.affdoc-preview-head { margin: 0 0 var(--s2); font-size: 0.95em; }
.affdoc-page-img { display: block; width: 100%; max-width: 760px; height: auto;
                   border: 1px solid var(--line-2); border-radius: 2px; background: #fff;
                   margin: 0 0 var(--s2); }
/* A value the page will PRINT but nobody has confirmed. Its own colour, because the whole point is
   that it is neither a blank (nothing to print) nor a settled value — and a reason shown only when
   the value is MISSING is a reason nobody sees on the run that needs it most. */
.affdoc-unconfirmed { border-left: 3px solid var(--warn, #a36b00); padding-left: var(--s2);
                      color: var(--warn, #a36b00); }

/* ============================================================== THE INFORMAL FLOW

   Two screens and a job page, replacing a five-step rail and sixteen stacked panels. The office's
   own words: "This is 1 page/step only, we do not need 1 to 5 steps in informal, remove all the
   steps we do."

   THE LAYOUT FOLLOWS THE SCREENS THE OFFICE SHOWED: a heading, a label column and a control
   column, and the three actions at the foot of the details screen.

   `min-width: 0` ON EVERY CONTROL IS LOAD-BEARING. A grid item's automatic minimum size is its
   CONTENT, not zero, so one long case number or a wide time-zone string otherwise widens the whole
   page instead of scrolling or wrapping inside its own box — the failure mode is a page that is
   fine in a test with short values and broken on the first real one. */
.inf-flow { display: flex; flex-direction: column; gap: var(--s3); }
.inf-tabs { display: flex; gap: var(--s3); flex-wrap: wrap; border-bottom: 1px solid var(--line);
            padding-bottom: var(--s2); }
.inf-tab { display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
           background: none; border: 1px solid transparent; border-bottom: 0; cursor: pointer;
           padding: var(--s2) var(--s3); font: inherit; color: var(--ink-3);
           border-radius: 3px 3px 0 0; }
.inf-tab:hover { color: var(--ink); }
.inf-tab.on { color: var(--ink); border-color: var(--line-2); background: var(--raised);
              font-weight: 600; }
.inf-tab-note { font-size: 0.72em; color: var(--ink-4); margin-top: 2px; }
.inf-card { min-width: 0; }
.inf-head { display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap;
            margin: 0 0 var(--s2); }
.inf-head h3 { margin: 0; }
.inf-count { color: var(--ink-3); font-size: 0.82em; }
.inf-empty { color: var(--ink-4); font-style: italic; }
.inf-quiet { color: var(--ink-4); font-size: 0.8em; }
.inf-warn { color: var(--warn, #a36b00); }

/* ---- 1. the documents to be served ---- */
.inf-docs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column;
            gap: var(--s3); }
.inf-doc { border: 1px dashed var(--line-2); border-radius: 3px; padding: var(--s3); min-width: 0; }
.inf-doc-head { display: flex; gap: var(--s2); align-items: baseline; flex-wrap: wrap; }
.inf-doc-file { font-size: 0.82em; color: var(--ink-3); overflow-wrap: anywhere; }
.inf-label { display: block; font-size: 0.85em; color: var(--ink-3); margin: var(--s2) 0 2px; }
.inf-input { width: 100%; max-width: 100%; min-width: 0; font: inherit; padding: 4px 7px;
             border: 1px solid var(--line-2); border-radius: 3px; background: var(--raised);
             color: var(--ink); box-sizing: border-box; }
.inf-input:focus-visible { outline: 2px solid var(--accent, #2f6f4f); outline-offset: 1px; }
textarea.inf-input { resize: vertical; font-family: inherit; }
.inf-doc-acts { display: flex; gap: var(--s2); margin: var(--s2) 0 0; flex-wrap: wrap; }
.inf-acts, .inf-next { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap;
                       margin: var(--s3) 0 0; }
.inf-next { justify-content: flex-end; }
/* THE DROP TARGET IS BIG ENOUGH TO BE ONE.
 *
 * The client: "drag and drop works though so make the drag and drop area bigger". MEASURED BEFORE:
 * the zone was authored 12px padding around one 17.85px line and came out 48px on the glass — SHALLOWER
 * than the 28px "Add files" button beside it, so it read as a rule under the list rather than a place
 * to put something.
 *
 * 120px, authored; 132px measured at the 1.1 root zoom, full width across the content column — 2.7x
 * its old height. The size comes from the usage scene, not taste: these are 10-2,000 page PDFs dragged
 * out of a file manager that may be maximised over or behind the browser, so the pointer is already
 * moving fast when it crosses the panel and the drop commits on button-release; a shallow strip is
 * simply missed. It stays a band INSIDE the panel it belongs to, so it never pushes the attached-file
 * list off the screen, and it keeps the incumbent 3px radius and --s2/--s3/--s4 spacing — a bigger
 * target is not a new visual language.
 *
 * FOUR CUES MAKE IT OBVIOUSLY A DROP TARGET, all incumbent tokens:
 *   1. a full-width band with the same dashed --line-3 frame `.inf-doc` already uses for "a container
 *      you put something into";
 *   2. a --sunken fill, so the band separates from the --surface card instead of reading as a hairline
 *      rule (ink-3 on sunken measures 5.33:1 light / 6.49:1 dark, so the label passes AA);
 *   3. the flex centring puts the label in the middle of the band, so the WHOLE rectangle is the
 *      target and not just the words;
 *   4. the drag-over state is strengthened — the incumbent only flipped border-colour, and at 132px a
 *      colour-only change is easy to miss while the operator's hand is already moving. A solid border
 *      says "committed" at exactly the moment they need to know (accent on accent-soft measures
 *      7.24:1 light / 6.59:1 dark).
 *
 * The `var(--accent, #2f6f4f)` green fallback is dropped: --accent is always defined in :root, so the
 * fallback was dead code from before the token existed.
 */
.inf-drop { margin: var(--s2) 0 0; padding: var(--s3) var(--s4); border: 1px dashed var(--line-3);
            border-radius: 3px; text-align: center; color: var(--ink-3); font-size: 0.85em;
            background: var(--sunken);
            min-height: 120px; display: flex; align-items: center; justify-content: center;
            transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease),
                        color var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.inf-drop.over { border-style: solid; border-color: var(--accent); background: var(--accent-soft);
                 color: var(--ink); box-shadow: var(--ring); }
/* ON A NARROW SCREEN THE TARGET GETS SHORTER (96px authored / ~106px measured): the panel is one
   column there and a 132px band would cost the attached-file list a fold for no gain, and a finger has
   a bigger contact point than a mouse, so it needs less depth rather than more. */
@media (max-width: 640px) {
  .inf-drop { min-height: 96px; }
}

/* ---- 2. the details ---- */
.inf-scriptbar { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap;
                 margin: var(--s2) 0; padding: var(--s2); border-radius: 3px;
                 background: var(--sunken, rgba(0,0,0,.03)); }
.inf-unread { margin: 0 0 var(--s2); padding-left: 1.1em; }
.inf-unread li { overflow-wrap: anywhere; }
.inf-groups { display: flex; flex-direction: column; gap: var(--s2); }
.inf-group { border: 1px solid var(--line); border-radius: 3px; padding: var(--s2) var(--s3);
             min-width: 0; }
.inf-group > summary { cursor: pointer; list-style: none; display: flex; gap: var(--s2);
                       align-items: baseline; flex-wrap: wrap; }
.inf-group > summary::-webkit-details-marker { display: none; }
.inf-group > summary::before { content: "\25B8"; color: var(--ink-4); font-size: 0.9em; }
.inf-group[open] > summary::before { content: "\25BE"; }
.inf-group-title { font-weight: 600; }
.inf-group-n { color: var(--ink-4); font-size: 0.78em; }
.inf-caption { margin: var(--s2) 0; }
.inf-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2) var(--s4); min-width: 0; }
.inf-field { min-width: 0; }
.inf-control { min-width: 0; }
.inf-req { color: var(--warn, #a36b00); font-size: 0.9em; }
.inf-flag { font-size: 0.72em; text-transform: uppercase; letter-spacing: .04em;
            color: var(--accent, #2f6f4f); border: 1px solid currentColor; border-radius: 2px;
            padding: 0 3px; margin-left: 4px; }
.inf-script, .inf-gap { margin: 2px 0 0; font-size: 0.76em; overflow-wrap: anywhere;
                        color: var(--ink-3); }
.inf-contested { margin: 2px 0 0; font-size: 0.8em; color: var(--warn, #a36b00);
                 overflow-wrap: anywhere; }
.inf-blank .inf-label { color: var(--ink-4); }

/* ---- email & share: NOTHING IS TICKED UNTIL THE OPERATOR TICKS IT ---- */
.inf-share { margin: var(--s4) 0 0; padding: var(--s3); border: 1px solid var(--line-2);
             border-radius: 3px; min-width: 0; }
.inf-share h4 { margin: 0 0 var(--s2); }
.inf-recip-group { margin: var(--s2) 0; padding: 0; list-style: none; }
.inf-recip-group h4 { margin: var(--s2) 0 2px; font-size: 0.78em; letter-spacing: .05em;
                      color: var(--ink-3); }
.inf-recip-group ul { list-style: none; margin: 0; padding: 0; }
.inf-recip label { display: flex; gap: var(--s2); align-items: baseline; cursor: pointer;
                   padding: 2px 0; flex-wrap: wrap; }
.inf-recip-name { min-width: 0; overflow-wrap: anywhere; }
.inf-recip-mail { color: var(--ink-3); font-size: 0.8em; overflow-wrap: anywhere; }
.inf-recip-note { color: var(--ink-4); font-size: 0.76em; }
.inf-star { color: var(--warn, #a36b00); }
.inf-other { margin: var(--s2) 0; }

/* ---- the three actions ---- */
.inf-actions { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap;
               margin: var(--s4) 0 0; padding-top: var(--s3); border-top: 1px solid var(--line); }
.inf-actions .inf-quiet { flex: 1 1 240px; min-width: 0; }
.btn.go { background: var(--accent, #2f6f4f); color: #fff; border-color: var(--accent, #2f6f4f); }
.btn.go:hover:not(:disabled) { filter: brightness(1.08); }
.inf-result { margin: var(--s2) 0 0; overflow-wrap: anywhere; }
.inf-result.bad { color: var(--danger, #a33); }

/* ---- 3. the job page ---- */
.inf-cols { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); min-width: 0; }
.inf-col { min-width: 0; }
.inf-col h4 { margin: var(--s3) 0 var(--s2); }
.inf-col h4:first-child { margin-top: 0; }
.inf-kv { display: grid; grid-template-columns: minmax(90px, auto) 1fr; gap: 2px var(--s3);
          margin: 0; min-width: 0; }
.inf-kv dt { color: var(--ink-3); font-size: 0.82em; }
.inf-kv dd { margin: 0; overflow-wrap: anywhere; }
.inf-list { list-style: none; margin: 0; padding: 0; }
.inf-list li { padding: 3px 0; border-bottom: 1px dashed var(--line); overflow-wrap: anywhere; }
.inf-attempt { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: baseline; }
.inf-attempt[data-ok="served"] { border-left: 3px solid var(--accent, #2f6f4f);
                                 padding-left: var(--s2); }
.inf-attempt-acts { display: inline-flex; gap: var(--s1, 6px); margin-left: auto; }
.inf-col-acts { display: flex; gap: var(--s2); margin: var(--s2) 0 var(--s3); flex-wrap: wrap; }
/* THE MACHINE'S ACT BUTTONS live beside the job header's status line now; the old "Where this job
   stands" footer block is gone at the client's word (24 Sep 2026). */
.inf-head-acts { margin: var(--s2) 0 0; }
.inf-elsewhere li, .inf-shut li { overflow-wrap: anywhere; }

/* ---- 5. the affidavit: a DOCUMENT, not a form ----
   WORKSTREAM B: `.aff-toolbar`'s box treatment moved to the editor chrome block (~line 4300) —
   the picker row is now a borderless band between the acts toolbar and the format toolbar. This
   older rule keeps only the layout the blocks OUTSIDE the editor (none today) would share; the
   editor's own row is restyled by the later block, which wins by order. */
.aff-toolbar { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap; }
.aff-notary { display: flex; gap: var(--s2); align-items: center; cursor: pointer;
              font-weight: 600; }
.aff-spacer { flex: 1 1 auto; }
.aff-page { border: 1px solid var(--line-2); border-radius: 3px; padding: var(--s4);
            background: var(--raised); display: flex; flex-direction: column;
            gap: var(--s3); min-width: 0; }
.aff-run { min-width: 0; }
.aff-run .inf-label { margin: 0 0 2px; font-size: 0.8em; }
.aff-blank .inf-label { color: var(--ink-4); }
.aff-preview { margin: var(--s2) 0 0; }
.aff-preview img { max-width: 100%; border: 1px solid var(--line-2); border-radius: 3px; }
/* A RENDER THAT DID NOT HAPPEN MUST NOT DRAW AS A BROKEN-IMAGE GLYPH. When the 1.png request
   fails (no documents yet, a render error), the bare `<img>` drew the browser's torn-page icon
   with the alt text spilling beside it — the audit's "broken image" finding. `onerror` swaps it
   for a stated empty state: same box, the reason in words. Degrade honestly (R8): the failure
   says itself instead of impersonating a preview. */
.aff-preview .aff-preview-empty {
  display: flex; align-items: center; gap: var(--s2);
  min-height: 120px; padding: var(--s3) var(--s4);
  border: 1px dashed var(--line-2); border-radius: 3px;
  color: var(--ink-3); font-size: 13px; background: var(--sunken);
}
/* A page image that failed to load keeps its box (no torn-page glyph): dashed frame + the reason
   in its alt text, drawn where the preview was. */
img.img-failed {
  display: block; min-height: 120px; padding: var(--s3);
  border: 1px dashed var(--line-2); border-radius: 3px;
  color: var(--ink-3); font-size: 13px; background: var(--sunken);
  font-family: var(--ui); letter-spacing: 0;
}

@media (max-width: 860px) {
  .inf-grid, .inf-cols { grid-template-columns: 1fr; }
  .inf-kv { grid-template-columns: 1fr; }
  .inf-kv dt { margin-top: var(--s2); }
}

/* ============================================================== DENSE PANEL PROSE ON A PHONE

   The job's own panels carry a lot of explanatory prose — each note is one or two sentences, and
   on a 390px screen a sentence is 100-300px tall. Measured: the contract-format panel came out at
   2741px against an 844px viewport, i.e. three phone screens of text before you reach the control
   underneath it. That is the "too complex" complaint in miniature, and it is worst exactly where
   the screen is smallest.

   So on a phone the LONG explanatory notes are clipped to a few lines and expandable, while the
   CONTROLS and the state lines stay fully visible. Nothing is hidden — the text is one tap away —
   and the panel becomes scannable instead of a wall. On a desk there is room, so nothing changes. */
@media (max-width: 640px) {
  /* the long notes inside the job's panels */
  .inf-subjects .pset-note,
  .inf-subjects .notes li {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* the states and every control stay whole: clipping THESE would hide what the panel is for */
  .inf-subjects .sig-state,
  .inf-subjects .set-line,
  .inf-subjects .req-card,
  .inf-subjects .att-quiet,
  .inf-subjects button,
  .inf-subjects .mono { -webkit-line-clamp: unset; display: revert; overflow: visible; }
  /* a panel's own trail list is a table of records, not prose — keep it readable */
  .inf-subjects .sig-trail, .inf-subjects .sig-trail li { -webkit-line-clamp: unset;
                                                          display: revert; overflow: visible; }
  .inf-cols { gap: var(--s3); }
  .inf-card { padding: var(--s3); }
}

/* ========================================================== A BLANK FIELD'S REASON, ONE HOVER AWAY

   Every unfilled field carries the reason it is unfilled, and that reason is real content (§6:
   "the documents do not say" is the honest answer, and a blank with a reason is not a defect).
   What is a defect is DRAWING ALL OF THEM AT ONCE. MEASURED on a fresh job (2026-09-25): 54 reason
   paragraphs = 1310px of the Job Details card's 5941px — a sentence under every blank field, so
   the fields the operator came to fill were buried under prose about the ones they had not.

   The client's ask was to lay this out like the office's own software, and that software draws a
   label above an input and NOTHING ELSE: no per-field paragraph at all. So the reason is not drawn
   by default; it is drawn the moment the operator touches the field it belongs to.

   WHY THIS IS NOT HIDING A FACT. The reason stays in the DOM in full — `display: none` keeps
   `innerText` and `textContent` intact (MEASURED in this headless build: a `display: none` element
   still answers `innerText` with its whole text), so a screen reader reads it, and any check that
   asserts on the reason's wording or counts the reasons still sees every one. It is one hover or
   one focus away, which is the same "one click from the source" the field's provenance keeps (§4),
   and the field's own `title` carries the whole reason as the native tooltip in the meantime. */
.inf-gap { display: none; margin: 2px 0 0; }
.inf-field:hover .inf-gap,
.inf-field:focus-within .inf-gap { display: block; }
.inf-gap[title] { cursor: help; }

/* ============================================================== A LINE MUST NOT CLIP ITS OWN VALUE

   Measured on a 390px phone: `.inf-input` inside an affidavit line reported
   `scrollWidth > clientWidth`, i.e. the control was narrower than the value inside it and the
   operator could not see the end of what a person will swear to. The cause is a grid/flex child's
   automatic minimum size — its CONTENT — so a long value widens the track instead of the input
   filling it, and `box-sizing` alone does not fix it. `min-width: 0` on the run and its control is
   what lets the input take the width it is given and scroll its own text instead of being clipped.
   This is the failure mode the project's own note names: "a field-level check passes happily while
   the rendered page clips a long value". */
.aff-run, .aff-run .inf-control, .aff-page .inf-field { min-width: 0; }
.aff-run .inf-input {
  width: 100%;
  max-width: 100%;
  min-width: 0;
  box-sizing: border-box;
  text-overflow: ellipsis;
}
.aff-run textarea.inf-input { overflow-x: auto; white-space: pre-wrap; overflow-wrap: anywhere; }
/* on a phone the document gets the full width of the card and nothing pads it out of the frame */
@media (max-width: 640px) {
  .aff-page { padding: var(--s3); }
  .aff-run .inf-label { font-size: 0.95em; }
}

/* ------------------------------------------------------- the affidavit as a DOCUMENT
   The client asked for the affidavit to open like a word processor and be fully editable. So the
   page is drawn as a sheet of paper with real document typography — not a form — and the only
   affordances around it are the template picker, the notary switch and the three actions.
   WORKSTREAM B: the editor's toolbar bands are styled once in the chrome block (~line 4300);
   these keep only the picker's own shape (and the dialog's reuse of it below). */
.affdoc .aff-toolbar { flex-wrap: wrap; gap: var(--s2); align-items: center; }
.aff-pick { display: inline-flex; flex-direction: column; gap: 4px; min-width: 22rem; }
.aff-pick .inf-input { width: 100%; }
.aff-choice { color: var(--ink-3); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }

/* ---- the create-affidavit template picker (the dialog `pickAffidavitTemplate()` appends) ----
   MEASURED DEFECT: the dialog was appended to <body> with these class names and NO rules anywhere
   in this file — so it painted as a bare block at the END of the page, below the fold. The job page
   stayed fully clickable behind it, the Affidavits button sat disabled on "Creating…", and the
   picker looked like a stray footer section rather than a question. The rules mirror the house
   `.modal-wrap` / `.modal` pair (line ~1094) exactly, tokens included, so dark theme and the
   phone bottom-sheet behave the same as every other dialog in this console. */
.aff-modal {
  position: fixed; inset: 0;
  background: rgba(20, 23, 27, 0.5);
  display: flex; align-items: center; justify-content: center;
  padding: var(--s4);
  z-index: 60;
  overscroll-behavior: contain;
}
.aff-modal-card {
  background: var(--surface);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xl);
  max-width: 520px; width: 100%;
  padding: var(--s5);
  box-shadow: var(--e3);
}
.aff-modal h3 { margin: 0 0 var(--s3); font-size: 17px; font-weight: 680; letter-spacing: -0.02em; }
.aff-modal .pset-note { margin: 0 0 var(--s4); }
@media (max-width: 720px) {
  /* dialogs: bottom sheet behaviour, so they land under the thumb — and `.aff-pick`'s 22rem
     min-width is wider than a 390px card once padded (measured shape of the 390px defects). */
  .aff-modal { align-items: flex-end; padding: 0; }
  .aff-modal .aff-modal-card {
    max-width: none; width: 100%;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    padding: var(--s4) var(--s4) max(var(--s4), env(safe-area-inset-bottom));
    max-height: 88vh; overflow-y: auto;
    overscroll-behavior: contain;
  }
  .aff-modal .aff-pick { min-width: 0; width: 100%; }
  .aff-modal .inf-actions .btn { flex: 1 1 0; min-height: 46px; }
}

.aff-doc-wrap { margin: var(--s3) 0 var(--s2); }
.aff-doc {
  background: #fff; color: #111;
  /* A SHEET, not a panel: the document is what gets signed, so it is drawn the way it will be
     read — plain white, generous margins, a serif face, and no console chrome inside it. */
  font-family: Georgia, "Times New Roman", serif;
  font-size: 15px; line-height: 1.55;
  padding: 2.4rem 2.6rem; border: 1px solid var(--line); border-radius: 3px;
  box-shadow: 0 1px 3px rgba(0,0,0,.10), 0 10px 24px -18px rgba(0,0,0,.35);
  max-width: 62rem; min-height: 26rem;
  white-space: pre-wrap; overflow-wrap: break-word;
  outline: none;
}
.aff-doc:focus { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.aff-doc .att-null { font-family: var(--sans, system-ui); font-size: 13px; color: #666; }
html[data-theme="dark"] .aff-doc { background: #f7f7f4; color: #111; }

.aff-factlist { margin: 0; padding-left: 1.1rem; }
.aff-factlist li { margin: 0 0 6px; }
.aff-where { color: var(--ink-3); font-size: 11px; }
.affdoc .inf-actions { display: flex; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s2); }

/* ------------------------------------------------- the email picker (one component, every share) */
.email-picker { border: 1px solid var(--line); border-radius: 6px; padding: var(--s2); margin: var(--s2) 0; }
.ep-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s2); }
.ep-search { margin: 6px 0; }
.ep-search .inf-input { width: 100%; }
.ep-results { max-height: 22rem; overflow-y: auto; border-top: 1px solid var(--line); }
.ep-entry { padding: 8px 0; border-bottom: 1px solid var(--line); }
.ep-entry-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ep-kind { font-size: 10px; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-3);
           border: 1px solid var(--line); border-radius: 3px; padding: 1px 5px; }
.ep-name { flex: 1 1 auto; }
.ep-n { color: var(--ink-3); font-size: 11px; }
.ep-all { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; }
.ep-addrs { list-style: none; margin: 4px 0 0; padding: 0 0 0 4px; }
.ep-addrs li { margin: 2px 0; }
.ep-addrs label { display: inline-flex; align-items: center; gap: 6px; }
.ep-label { font-size: 11px; color: var(--ink-3); }
.ep-manual { margin-top: var(--s2); }
.ep-manual-row { display: flex; gap: 6px; align-items: center; }
.ep-manual-row .inf-input { flex: 1 1 auto; }
.ep-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.ep-chip { display: inline-flex; align-items: center; gap: 4px; border: 1px solid var(--line);
           border-radius: 999px; padding: 2px 8px; background: var(--raised); }
.ep-x { border: 0; background: transparent; cursor: pointer; font-size: 14px; line-height: 1;
        color: var(--ink-3); }
.ep-x:hover { color: var(--danger, #c0392b); }

/* ---------------------------------------------------------- the attempt report picker */
.report-picker { border: 1px solid var(--line); border-radius: 6px; padding: var(--s2); margin: var(--s2) 0; }
.rp-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rp-count { color: var(--ink-3); margin-left: auto; }
.rp-list { list-style: none; margin: 6px 0 0; padding: 0; }
.rp-row { padding: 6px 0; border-bottom: 1px solid var(--line); }
.rp-row label { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rp-served { border-left: 3px solid var(--accent); padding-left: 8px; }

/* ------------------------------------------------------------- the directory overlay */
/* THE DIRECTORY OVERLAY FOLLOWS THE CONSOLE'S OWN PATTERN (.src-overlay): `position: fixed; inset:
   0` with a FULL background colour, not a translucent one over the page. A translucent sheet let
   the case cards and the toolbar show through the dialog — measured in a screenshot, the client's
   own title bar read straight through "Clients, employees & servers" — which makes a form
   unreadable and looks broken. The console's other full-screen surfaces already do it this way, and
   the tokens are the ones they use. */
.dir-overlay {
  position: fixed; inset: 0; z-index: 60;
  background: var(--bg); padding: var(--s4);
  display: flex; flex-direction: column;
  overflow-y: auto; overscroll-behavior: contain;
}
.dir-overlay[hidden] { display: none; }
.dir-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-xl);
            max-width: 68rem; width: 100%; margin: 0 auto;
            padding: var(--s4); }
.dir-search { display: flex; align-items: center; gap: 8px; margin: var(--s2) 0; }
.dir-search .inf-input { flex: 1 1 auto; }
.dir-new { border: 1px solid var(--line); border-radius: 6px; padding: var(--s2); margin-bottom: var(--s2); }
.dir-new .inf-input { width: 100%; margin-bottom: 6px; }
.dir-emails-head { display: flex; justify-content: space-between; align-items: center; margin-top: 6px; }
.dir-email-row { display: flex; gap: 6px; margin-bottom: 4px; }
.dir-email-row .inf-input { flex: 1 1 auto; margin: 0; }
.dir-new-acts { display: flex; align-items: center; gap: var(--s2); margin-top: 8px; }
.dir-list { margin-top: var(--s2); }
.dir-entry { border: 1px solid var(--line); border-radius: 6px; padding: var(--s2); margin-bottom: 8px; }
.dir-entry-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dir-n { color: var(--ink-3); }
.dir-addrs { list-style: none; margin: 4px 0 0; padding: 0; }
.dir-addrs li { margin: 2px 0; }
.pset-h { margin: var(--s2) 0 4px; }
.brief-kv { margin-top: 4px; }

/* ================================================================== NOTIFICATIONS (E6.4)
   The feed and its pop-ups. Material, not a flat list: each row is a surface that lifts slightly on
   hover, and the kind is carried by a small coloured mark rather than by shouting the whole line.
   The pop-up is a real material — translucent, blurred, with a bright top edge — and it NEVER blocks:
   no scrim, no pointer capture, so the operator keeps working while it is there. */
.nf-item {
  display: flex; flex-direction: column; gap: 6px;
  padding: 12px 14px; margin-bottom: 8px;
  border: 1px solid var(--line); border-radius: 10px;
  background: color-mix(in srgb, var(--raised) 92%, transparent);
  transition: border-color 160ms ease, transform 160ms cubic-bezier(.2,.7,.3,1);
}
.nf-item:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.nf-item.read { opacity: .62; }
.nf-item.read .nf-head { font-weight: 400; }
.nf-top { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.nf-kind {
  font: 600 10.5px/1 var(--mono, ui-monospace, monospace);
  letter-spacing: .04em; text-transform: uppercase;
  padding: 3px 7px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--ink-3);
}
.nf-kind.warn { border-color: var(--warn-line); color: var(--warn); }
.nf-kind.bad  { border-color: var(--danger-line); color: var(--danger); }
.nf-kind.good { border-color: var(--ok-line); color: var(--ok); }
.nf-case { font-weight: 600; cursor: pointer; }
.nf-case:hover { text-decoration: underline; text-underline-offset: 2px; }
.nf-when { margin-left: auto; font-size: 12px; color: var(--ink-3); }
.nf-head { margin: 0; font-size: 13.5px; line-height: 1.5; }
.nf-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.nf-deliv { font-size: 12px; }
.nf-deliv.good { color: var(--ok); }
.nf-deliv.bad  { color: var(--danger); }
.nf-deliv.quiet { color: var(--ink-3); font-style: italic; }
#notify-open.has-unseen { border-color: color-mix(in srgb, var(--accent) 60%, var(--line)); }

/* ---- the pop-ups: a material layer, never a modal ---- */
.toasts {
  position: fixed; right: 18px; bottom: 18px; z-index: 30;
  display: flex; flex-direction: column-reverse; gap: 10px;
  max-width: min(380px, calc(100vw - 36px)); pointer-events: none;
}

/* A POP-UP MUST NOT SIT ON TOP OF AN OPEN PANEL. Measured: with the Notifications panel open, three
   toast cards covered the feed's own rows and the panel's buttons — the operator could not read what
   the pop-up was telling them about. A toast is ambient; a panel is the thing being worked in, so the
   panel wins and the pop-ups step aside rather than stacking over it.
   They are hidden rather than removed, so they come back when the panel closes without re-announcing
   (an announcement already made is not made twice). */
body:has(.form-overlay:not([hidden])) .toasts { opacity: 0; pointer-events: none; }
.toasts { transition: opacity 180ms ease; }
@media (prefers-reduced-motion: reduce) { .toasts { transition: none; } }
.toast {
  pointer-events: auto;
  background: color-mix(in srgb, var(--raised) 78%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  backdrop-filter: blur(18px) saturate(180%);
  border: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
  border-top-color: var(--line-2);
  border-radius: 12px; padding: 12px 14px;
  box-shadow: 0 12px 34px rgba(0,0,0,.30), 0 2px 6px rgba(0,0,0,.18);
  animation: toast-in 320ms cubic-bezier(.2,.8,.25,1) both;
}
.toast-top { display: flex; align-items: baseline; gap: 8px; margin-bottom: 5px; }
.toast-case { font-weight: 600; font-size: 12.5px; }
.toast-text { margin: 0 0 9px; font-size: 13px; line-height: 1.45; }
.toast-acts { display: flex; gap: 7px; }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(10px) scale(.97); }
  to   { opacity: 1; transform: none; }
}
/* Reduced motion: the pop-up ARRIVES, it just does not travel. Opacity still changes, because
   appearing is the whole point of a notification and a cross-fade carries that fine. */
@media (prefers-reduced-motion: reduce) {
  .toast { animation: toast-fade 220ms ease both; }
  .nf-item { transition: none; }
}
@keyframes toast-fade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-transparency: reduce) {
  .toast { background: var(--raised); backdrop-filter: none; }
}

/* ============================================================================
   THE AFFIDAVIT GENERATOR'S CHROME — Workstream B, 24 Sep 2026 (impeccable · Operate,
   with the apple-design feel).

   The generator is ONLY: the acts toolbar (Save / Cancel / Share), the template picker, the
   format toolbar, the document. The chrome below is the material those four pieces are made
   of: a translucent toolbar layer the sheet scrolls beneath (apple-design §12 — content
   under the chrome, not bars that consume a fixed strip), instant press feedback on
   :active (§1 — respond on pointer-down), tokens throughout, dark theme carried by the
   tokens, and the reduced-motion / reduced-transparency equivalents stated explicitly.

   EVERY selector here was measured against the editor's own DOM (`infAffidavit` in app.js):
   `.affdoc` is the card (`section.step-card.inf-card.affdoc`), `.affed-actions` the acts
   toolbar (`#aff-acts`), `.aff-toolbar` the picker row, `.aff-fmt` the format toolbar,
   `.affed-sheet` (`#aff-doc`) the editable sheet, `.affed-pagewrap` its scroller.
   ============================================================================ */

/* ---- THE SHEET: pages of paper on a sunken desk ---- */
/* One `.aff-page` block per template page (the engine's run `page` field; see
   `informal_affidavit_doc.render_html`, which paginates the PDF the same way), and
   `.aff-line` per run carrying the run's own `style` (align/bold/italic/underline/
   size_pt/colour) as inline CSS — the same keys the PDF renderer styles from, so the
   screen cannot disagree with what prints. A legacy record draws neither and reads
   exactly as it did before. */
.affed-sheet { display: block; }
.affed-sheet .aff-page { display: block; background: inherit; }
.affed-sheet .aff-page + .aff-page {
  margin-top: 26px; padding-top: 26px; border-top: 1px dashed var(--line-2);
}
.affed-sheet .aff-line { display: block; min-height: 1em; }
.affed-sheet .aff-line:empty::after { content: "\00a0"; }

/* ---- THE PAGE DRAWN AT THE COURT FORM'S OWN MEASUREMENTS ------------------------------------
   Each `.aff-page` block is sized in POINTS from the template's own `page_geometry` (written
   inline by the renderer) and each `.aff-line` is absolutely positioned at its own measured
   spot, with its own `line-height` — so the sheet on screen is the form's page, not a browser's
   idea of a document. The engine's PDF renderer draws the same way from the same numbers
   (`informal_affidavit_doc.render_html`), so the two cannot drift (R9).

   The rules here are the ONES A STYLE ATTRIBUTE CANNOT CARRY, and nothing else: the inline
   style already says where each line is, how big it is and what colour it is. */

/* A PAGE LAID OUT AT THE FORM'S OWN POSITIONS CARRIES NO PADDING (the renderer writes `padding:0`
   inline on such a page; this is the belt to that pair of braces). An absolutely positioned
   child is measured from its containing block's PADDING box — CSS 2.1 §10.1, measured in this
   browser — and a placed line's coordinates already come from the sheet's top-left, so a page
   that also carried the form's margins as padding would push every line a margin too far right
   and down. The engine's own renderer states the same rule (`.page.abs { padding: 0 !important }`).
   `overflow: hidden` is the form's own behaviour: the form clips at its edge, it does not grow,
   and a page that silently grew would move every page break below it. */
.affed-sheet .aff-page.abs { padding: 0 !important; overflow: hidden; position: relative; }

/* THE FORM'S OWN FIELD BOXES AND RULES, UNDER THE LINES. On a court form much of what the eye
   reads as the form IS these boxes — the light blue fields and the grey rules. They are drawn at
   their measured rects, BEFORE the lines in DOM order so a value sits on its own field rather than
   under a line of text. A box that is NOT a field is decoration in the CSS sense only: no border-box
   growth, no pointer, no selection, and `contenteditable="false"` in the markup so the browser never
   puts a caret in one (they contribute no line to the text Save reads — measured). */
.affed-sheet .fbox { position: absolute; box-sizing: border-box; pointer-events: none;
  user-select: none; }

/* ---- THE BLUE BOX IS THE FIELD, AND IT IS TYPEABLE ------------------------------------------
   THE CLIENT'S OWN DEFECT, and it was not the position: every blue rect was already drawn at its
   measured PDF coordinate. What was wrong was that the box was a `pointer-events: none` decoration
   while the line's text — which IS the field's value, because the engine appends it to the label's
   own run — was drawn as a `.aff-line` flowing from the LABEL's left edge. So the operator typed
   over the printed words and could not click the box ("I can't properly type in where there should be
   a text box (but there isn't)").
   Now the box IS the input: it takes the pointer, it holds a caret, it is the thing the operator
   clicks and types in, and the value it shows is that line's own value read from the record. */
.affed-sheet .fbox.aff-field {
  pointer-events: auto; user-select: text;
  display: flex; align-items: center; gap: 3pt;
  padding: 0 3pt; overflow: hidden;
  font-size: 7.5pt; line-height: 1;
  color: #14171b; cursor: text;
}
/* THE FORM'S PRINTED WORDS STAY PRINTED WORDS. The label the form prints is drawn INSIDE the box as
   a non-editable span, so the operator reads it where the form puts it and can never type over it.
   It is `user-select: none` and `contenteditable="false"`, so it contributes nothing to the text
   Save reads. A box whose label the form prints OUTSIDE it (the caption's `TELEPHONE NO:` sits left
   of its field) carries no span: the label is the line's own printed text beside the box. */
.affed-sheet .fbox .fbox-label { color: #14171b; white-space: nowrap; user-select: none;
  pointer-events: none; }
/* THE VALUE THE OPERATOR TYPES. It is the editable part of the box, and it is the line's own value:
   what is typed here is sent as that line, so the field and the document cannot disagree. A box with
   no value from the job is drawn EMPTY and is still a clickable write-in area — absent means blank,
   with the box there to type into (the client's hardest rule: the template's own example wording is
   never put here).

   THE VALUE FILLS THE BOX'S WHOLE HEIGHT, and that is the client's own complaint being closed. It was
   `flex: 1 1 auto` inside an `align-items: center` box with a 7.5pt font in an 11pt rect, so the
   editable span occupied a thin band in the middle and the rest of the blue was the box's own
   padding — which is `pointer-events: none` on the wrapper (see `.fbox`) and therefore fell through
   to the sheet. Measured as the operator describes it: "you have to get the cursor like on exactly the
   bottom line of it to get to type in it", and a click in the blue above that band was taken by the
   LINE's handler as an advanced edit. Now the span is `align-self: stretch` and fills the rect, so a
   click anywhere the operator sees a field lands IN the field. The printed geometry is untouched:
   nothing here changes the box's position or size, only which element inside it takes the pointer. */
.affed-sheet .fbox .fbox-value { flex: 1 1 auto; min-width: 0; outline: none; align-self: stretch;
  display: flex; align-items: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: text;
  border-bottom: 0.5pt solid transparent; }
.affed-sheet .fbox.aff-field:hover .fbox-value {
  border-bottom-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.affed-sheet .fbox .fbox-value:focus {
  border-bottom-color: var(--accent);
  background: color-mix(in srgb, #fff 55%, transparent); }
/* A FIELD WITH NO VALUE YET says so by being a visible target rather than an invisible one: the
   write-in rule is drawn on hover and focus only, so an empty form box does not look like a filled
   one — but a click anywhere in the blue lands the caret in the field. */
.affed-sheet .fbox.aff-field:empty .fbox-value { min-height: 1em; }

/* A PAGE THAT HOLDS NO MEASURED LINE DRAWS IN FLOW, and it keeps room to be typed in: the form's
   own page is a sheet of paper, so the box is the page's size whether or not a line landed on it.
   This is the fallback path (an older stored record), and it draws exactly as the editor did
   before. */
.affed-sheet .aff-page:not(.abs) { padding: 36pt 54pt 0; }

/* ---- THE DRAWN SHEET IS A DRAWING OF PAPER, NOT A SHEET TO SCROLL ----
   `.paged` is added by the editor only when the markup really holds page blocks (see
   `infAffidavit`), so a legacy document — plain lines, one sheet — keeps every rule it had. When
   the pages ARE the paper, the sheet's own paper, padding and text wrapping come off: the page
   blocks carry the template's own size and margins, and a document laid out at the form's own
   coordinates must not also be flowed or padded by the house sheet.

   `white-space: pre-wrap` is the one that would silently break the drawing: the page's own
   dimensions and every line's place come from the record, and a wrapping rule on the container
   turns a line the form prints on one row into two rows. */
.affed-sheet.paged {
  background: transparent; border: 0; border-radius: 0; box-shadow: none;
  padding: 0; min-height: 0; max-width: none; white-space: normal;
  line-height: normal; width: fit-content;
}
/* THE PAGE SITS IN THE MIDDLE OF THE SPACE IT HAS, as a sheet of paper on a desk does.
   `margin-inline: auto` is NOT enough here: the pagewrap is a column flex container, and in one the
   cross axis is horizontal — a flex item is stretched or aligned by `align-items`, and its auto
   margins only take effect once it is not stretched. Measured: with the auto margin alone the CR-125
   page drew hard against the left of a 1306px scroller (the legacy sheet centred, so this was a
   difference introduced by the drawing). */
.affed-pagewrap { align-items: center; }
/* …and the sheet is the only child that must not be stretched by that alignment, so it is told its
   own width (`fit-content`, above) and the wrapper's child is not grown. */
.affed-pagewrap > .affed-sheet { flex: 0 0 auto; }
/* THE SHEET IS STILL THE EDITABLE DOCUMENT, so it still answers to focus — with a ring on the PAPER
   rather than a box-shadow around a container that is now only as big as the page. */
.affed-sheet.paged:focus { box-shadow: none; }
.affed-sheet.paged:focus .aff-page { outline: 2px solid
  color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 2px; }
/* THE PAGE BLOCKS ARE THE PAPER. `background: inherit` above exists for the legacy sheet (it took
   the sheet's own tint); a drawn page is white, like the sheet it came from. */
.affed-sheet.paged .aff-page { background: #fff; }

/* ---- THE PAGES ARE SCALED TO FIT THE SCREEN, AND THE DOCUMENT IS NOT ----
   The editor measures the room and writes `transform: scale(k)` plus the scaled size here (see
   `affFitPages`). The scale is a DISPLAY scale: every point length, font size and line height
   inside is the form's own and stays that way, so the page on screen is the page that prints. */
.affed-sheet .aff-pages { transform-origin: top left; }
/* A SCALED BLOCK STILL RESERVES ITS UNSCALED BOX, so the wrapper is given the scaled size inline
   and the page blocks are taken out of its flow — otherwise the sheet would be twice as tall as
   what is drawn on it. */
.affed-sheet.paged .aff-page { position: relative; }
.affed-sheet .aff-pages .aff-page + .aff-page {
  margin-top: 26px; padding-top: 0; border-top: 0;
  box-shadow: 0 -13px 0 rgb(0 0 0 / 0.10);      /* the edge of the sheet under it */
}

/* ---- THE ACTS TOOLBAR: a translucent material the sheet scrolls under ---- */
.affed-actions {
  position: sticky; top: 0; z-index: 6;
  display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap;
  margin: 0; padding: 8px 12px;
  background: color-mix(in srgb, var(--surface) 72%, transparent);
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  backdrop-filter: blur(20px) saturate(160%);
  border-bottom: 1px solid var(--line);
}
/* The bright top edge — light catching the material (apple-design §12). */
.affed-actions::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 1px; pointer-events: none;
  background: color-mix(in srgb, var(--raised) 55%, transparent);
}
.affed-actions .affed-status { margin-inline-start: auto; }
.affed-actions .affed-tpl {
  color: var(--ink-3); font-size: 11px; letter-spacing: .02em;
  max-width: 22ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.affed-actions .affed-editing { margin-inline-start: var(--s2); }

/* ---- THE PICKER ROW: the picker, and nothing else ---- */
.aff-toolbar {
  display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap;
  padding: 6px 12px; margin: 0;
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  border-bottom: 1px solid var(--line);
}
.affdoc .aff-toolbar { flex-wrap: wrap; gap: var(--s2); align-items: center; }
.aff-pick { display: inline-flex; flex-direction: column; gap: 3px; min-width: 22rem;
  flex: 1 1 18rem; }
.aff-pick .inf-label { font-size: 11px; color: var(--ink-3); letter-spacing: .04em; }
.aff-pick .inf-input { width: 100%; }
.aff-choice { color: var(--ink-3); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }

/* ---- THE FORMAT TOOLBAR: tight, sticky, and the document's own tools ---- */
.aff-fmt {
  display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
  padding: 6px 10px; margin: 0;
  background: color-mix(in srgb, var(--surface) 62%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 53px; z-index: 5;
}
/* THE TOOLBAR STAYS PUT while the document scrolls under it — a word processor's toolbars do, and a
   bold button that scrolls away is a bold button the operator has to hunt for mid-sentence. */
.aff-fmt-btn { min-width: 32px; padding: 3px 8px; font-size: 13px; line-height: 1.4;
  border-color: transparent; }
.aff-fmt-btn:hover:not(:disabled) { border-color: var(--line-2); }
.aff-fmt-b { font-weight: 800; }
.aff-fmt-i { font-style: italic; font-family: Georgia, serif; }
.aff-fmt-u { text-decoration: underline; }
.aff-fmt-sep { width: 1px; align-self: stretch; background: var(--line); margin: 0 4px; }
.aff-fmt-size { font-size: 12px; padding: 3px 6px; border: 1px solid var(--line-2);
  border-radius: var(--r-sm); background: var(--surface); color: var(--ink); }
.aff-fmt-state { font-size: 11px; color: var(--ink-4); }
/* The notary switch rides at the toolbar's end, beside the tools it qualifies. */
.aff-fmt .aff-notary { margin-inline-start: 2px; }

/* ---- INSTANT PRESS FEEDBACK on the generator's own controls ----
   The console's `.btn:active` already answers on pointer-down (apple-design §1: feedback on
   the press, not the release). The generator's tools get the same answer with a slight scale
   — a press you can SEE at the moment it happens — released by the shared transition. */
.aff-fmt-btn:active:not(:disabled) { transform: scale(0.94); }
.affed-actions .btn:active:not(:disabled) { transform: scale(0.97); }
@media (prefers-reduced-motion: reduce) {
  .aff-fmt-btn:active:not(:disabled), .affed-actions .btn:active:not(:disabled) { transform: none; }
}

/* ---- REDUCED TRANSPARENCY: the materials go frosty, not opaque-flat ---- */
@media (prefers-reduced-transparency: reduce) {
  .affed-actions, .aff-toolbar, .aff-fmt { background: var(--surface); backdrop-filter: none; }
}

/* ---- 390px PHONE: the chrome stays one column and nothing drags sideways ----
   The picker's 22rem floor is wider than a 390px screen once padded, so it drops to full width;
   the acts bar lets its status wrap under the buttons instead of squeezing them. */
@media (max-width: 720px) {
  .affed-actions { padding: 8px 10px; gap: var(--s2); }
  .affed-actions .affed-status { flex-basis: 100%; margin-inline-start: 0; }
  .affed-actions .affed-tpl { display: none; }   /* the picker below names the template */
  .aff-toolbar { padding: 6px 10px; }
  .aff-pick { min-width: 0; width: 100%; flex-basis: 100%; }
  .aff-fmt { position: static; top: auto; }      /* sticky bands eat a 844px-tall screen */
  .affed-sheet { padding: 28px 20px; font-size: 13px; }
}

/* ---- THE DOCUMENT COLUMN: sheet on a sunken ground, gap prose gone ---- */
.affed-pagewrap { padding-bottom: var(--s6); }
/* THE CHANGE LOG FOLLOWS THE WORK. It stays on the affidavit view (the E4.10 check opens this
   view and asserts the panel), but below the fold of the document — the record of changes, not
   part of the working surface. */
.aff-log-wrap { margin: var(--s4) 0 0; }

/* ============================================================================
   LOADING — a skeleton that holds the shape of what is coming
   ============================================================================
   The old loading state was one line of grey text in an empty pane: the screen reflowed from
   nothing into a full layout, and the operator could not tell whether it was working or stuck.
   This holds the structure instead, so the page settles rather than jumps (Apple: hint in the
   direction of the outcome). The shimmer is slow and low-contrast on purpose — a loading
   indicator should be calm, not a strobe.
   ============================================================================ */
.skel { padding: 26px 24px; display: flex; flex-direction: column; gap: 14px; }
.skel-head { display: flex; align-items: center; gap: 12px; }
.skel-sub, .skel-row { display: flex; align-items: center; gap: 14px; }
.skel-row { padding-top: 10px; border-top: 1px solid var(--line); }
.skel-bar, .skel-chip {
  display: block; height: 12px; border-radius: 6px;
  background: linear-gradient(90deg, var(--line) 0%, var(--raised) 50%, var(--line) 100%);
  background-size: 200% 100%;
  animation: skel-sweep 1.6s ease-in-out infinite;
}
.skel-chip { height: 20px; width: 76px; border-radius: 999px; }
.skel-bar.w40 { width: 40%; height: 17px; }
.skel-bar.w34 { width: 34%; }
.skel-bar.w30 { width: 30%; }
.skel-bar.w26 { width: 26%; }
.skel-bar.w22 { width: 22%; }
.skel-bar.w18 { width: 18%; }
.skel-bar.w14 { width: 14%; }
.skel-note { margin: 6px 0 0; color: var(--muted); font-size: 13px; letter-spacing: 0.01em; }
@keyframes skel-sweep { 0% { background-position: 180% 0; } 100% { background-position: -80% 0; } }

/* ---------------------------------------------------------------------------
   FEEL — instant press feedback, and motion that does not fight the operator
   ---------------------------------------------------------------------------
   `:active` on the press, never on release: feedback on touch-DOWN is what makes a control feel
   connected to the finger. Transform + opacity only, so it stays on the compositor.
   --------------------------------------------------------------------------- */
button:not(:disabled):active,
.wf-tab:active,
.home-card:active .home-card-main,
.case-back:active { transform: scale(0.98); }
button, .wf-tab, .home-card, .nf-item {
  transition: transform 110ms cubic-bezier(0.2, 0, 0, 1), background-color 140ms ease,
              border-color 140ms ease, opacity 140ms ease;
}
button:disabled { opacity: 0.5; cursor: not-allowed; }
/* The delete control must never look available while the case is still loading — the action has no
   valid target yet, and a destructive control the operator cannot judge is worse than none. */
#delete-case[data-loading] { opacity: 0.4; }

/* Reduced motion: keep the feedback, drop the movement (Apple §14). */
@media (prefers-reduced-motion: reduce) {
  .skel-bar, .skel-chip { animation: none; background: var(--line); }
  button, .wf-tab, .home-card, .nf-item { transition: opacity 120ms ease; }
  button:not(:disabled):active, .wf-tab:active,
  .home-card:active .home-card-main, .case-back:active { transform: none; }
}

/* More contrast: the skeleton must stay visible when the operator has asked for contrast. */
@media (prefers-contrast: more) {
  .skel-bar, .skel-chip { background: var(--ink); opacity: 0.28; animation: none; }
  .skel-row { border-top-color: var(--ink); }
}

/* The notification counts: separate chips that never split a number from its word. */
.nf-count {
  display: inline-flex; align-items: center; white-space: nowrap;
  color: var(--muted); font-size: 12.5px; letter-spacing: 0.01em;
}
.nf-count + .nf-count::before { content: "·"; margin: 0 6px; color: var(--ink-4); }
#nf-summary { display: flex; flex-wrap: nowrap; gap: 0; align-items: baseline; white-space: nowrap; }

/* ---------------------------------------------------------------------------
   PHONE WIDTH — the workflow tabs and the job tabs must not be cut off
   ---------------------------------------------------------------------------
   Seen in a capture at 390px: the top-right "Jobs" tab was clipped off the right edge, and on the
   job page "Documents to be serv[ed]" and "Job Details" ran to the edge with their last letters
   chopped. Both rows are horizontal flex lists of fixed-ish items, so at phone width they simply
   overflowed. They now WRAP instead of being cut: a second line is honest, a clipped label is not.
   --------------------------------------------------------------------------- */
@media (max-width: 700px) {
  .wf-tabs { flex-wrap: wrap; }
  .wf-tab { flex: 0 1 auto; }
  .inf-tabs { gap: var(--s2) var(--s3); }
  .inf-tab { min-width: 0; }
  .inf-tab .inf-tab-label { white-space: normal; overflow-wrap: anywhere; }
}
/* Long values (a job's title, a case id) must wrap rather than run under the edge. */
.entry-name, .home-card-name, .inf-head h3 { overflow-wrap: anywhere; }

/* ---------------------------------------------------------------------------
   A MONOSPACED NOTE MUST BREAK, NOT OVERFLOW
   ---------------------------------------------------------------------------
   Measured at 390px: `.inv-panel` and its `.pset-note.mono` were 276px of content inside 273px of
   box — a 3px overflow, which is enough to clip the last character of a sentence and enough to trip
   the "nothing is clipped" audit. Monospaced text is wider per character and has no word-break
   rules of its own, so it needs an explicit one. `overflow-wrap: anywhere` (not `break-word`) is
   what lets a long unbroken note wrap instead of pushing the box wider than its parent.
   --------------------------------------------------------------------------- */
.pset-note, .pset-note.mono, .inv-panel, .att-panel { min-width: 0; overflow-wrap: anywhere; }

/* PHONE: the pop-ups must not become the page.
   Measured at 390x844: three stacked toasts covered the job's tabs and most of its text. On a phone
   they are limited to one, given the full width of the margin box, and kept to a single line each —
   a notification is an interruption, not a reading task. */
@media (max-width: 700px) {
  .toasts { left: 12px; right: 12px; bottom: 12px; }
  .toast { padding: 10px 12px; }
  .toast-text { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
                overflow: hidden; }
}

/* ---------------------------------------------------------------------------
   CONDITIONAL FIELDS — the field's own rule decides whether it is on screen
   ---------------------------------------------------------------------------
   The office's screenshots: Individual shows the party's own details, Organization shows *Agent for
   Service* instead; an Employee process server picks a name from its roster, a Contractor is typed
   fields. Hidden means `hidden`, so it takes no space and is not focusable — a field that is not
   shown must not be tabbable into.
   --------------------------------------------------------------------------- */
.inf-field[hidden], .inf-conditional-hidden { display: none !important; }
.inf-grid-empty { display: none; }
/* A conditional field that disappears must not leave the grid looking broken: the gap it held closes
   with it, which `hidden` already does. */

/* ============================================================================
   THE AFFIDAVIT GENERATOR — the three-pane editor
   ============================================================================
   Built to the office's own reference screens: a FIELDS column on the left holding every job field
   grouped as the job record groups them, the DOCUMENT in the middle as the page itself, and the
   TEMPLATE column on the right holding the template's own answers. The document is the centre of
   the screen, because it is the thing being produced — not a form with a preview beside it.

   Colours and spacing come from this console's tokens, so the editor is the same product as the
   rest of the console rather than a screen borrowed from another one.
   ============================================================================ */
.affed { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; margin: var(--s3) 0 0;
         border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden;
         background: var(--bg); min-height: 560px; }
.affed-col { min-width: 0; display: flex; flex-direction: column; }
.affed-left  { border-right: 1px solid var(--line); background: var(--surface); }
.affed-right { border-left: 1px solid var(--line); background: var(--surface); }
.affed-mid   { background: var(--sunken); }

/* the column headings, in the office's own words */
.affed-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s2);
              padding: var(--s3) var(--s3) var(--s2); border-bottom: 1px solid var(--line); flex: none; }
.affed-title { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em;
               color: var(--ink-3); }
.affed-search { padding: var(--s2) var(--s3); border-bottom: 1px solid var(--line); flex: none; }
.affed-search input { width: 100%; }
.affed-scroll { overflow-y: auto; flex: 1; min-height: 0; padding: var(--s2) 0 var(--s4); }

/* ---- the field palette: one group per job-record group, in the record's own order ---- */
.affed-group { border-bottom: 1px solid var(--line); }
.affed-group:last-child { border-bottom: 0; }
.affed-group > summary { display: flex; align-items: center; justify-content: space-between;
  gap: var(--s2); padding: 9px var(--s3); cursor: pointer; list-style: none;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3); }
.affed-group > summary::-webkit-details-marker { display: none; }
.affed-group > summary:hover { color: var(--ink); }
.affed-group-n { font-weight: 600; color: var(--ink-4); letter-spacing: 0; }
.affed-fields { padding: 2px 0 8px; }
/* One field: the token the template writes, and what the record holds for it. A field with no
   value says so rather than showing an empty chip — the editor must not read as though a value
   exists (R1). */
.affed-field { display: flex; align-items: baseline; gap: 8px; width: 100%; text-align: left;
  padding: 5px var(--s3) 5px var(--s3); border: 0; background: transparent; cursor: pointer;
  font: inherit; color: var(--ink-2); }
.affed-field:hover { background: var(--hover); color: var(--ink); }
.affed-token { font-family: var(--mono); font-size: 11px; color: var(--accent); flex: none;
               overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 58%; }
.affed-val { font-size: 11px; color: var(--ink-4); overflow: hidden; text-overflow: ellipsis;
             white-space: nowrap; flex: 1; text-align: right; }
.affed-field.affed-blank .affed-val { font-style: italic; color: var(--ink-4); }
.affed-field.affed-blank .affed-token { color: var(--ink-3); }

/* ---- the document, on its own sheet in the middle ---- */
.affed-pagewrap { overflow: auto; flex: 1; min-height: 0; padding: var(--s4) var(--s3); }
.affed-sheet { background: #fffdf8; color: #14171b; margin: 0 auto; max-width: 780px; min-height: 520px;
  padding: 44px 52px; border: 1px solid var(--line-2); border-radius: 3px;
  box-shadow: 0 8px 28px rgba(0,0,0,0.18);
  font-family: "Times New Roman", Times, serif; font-size: 14px; line-height: 1.72; white-space: pre-wrap; }
.affed-sheet:focus { outline: none; box-shadow: 0 8px 28px rgba(0,0,0,0.18), 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); }
.affed-sheet .att-null { font-family: var(--sans, system-ui); font-size: 12px; color: #7a6252;
  background: #f6ecd9; border-bottom: 1px dashed #b99a6b; padding: 0 3px; }

/* ---- the template column ---- */
.affed-card { padding: var(--s3); border-bottom: 1px solid var(--line); }
.affed-card h4 { margin: 0 0 6px; font-size: 10.5px; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3); }
.affed-card p { margin: 0 0 8px; font-size: 12px; color: var(--ink-3); line-height: 1.5; }
.affed-key { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px;
  padding: 3px 9px; border: 1px solid var(--line-2); border-radius: var(--r-pill);
  background: var(--raised); color: var(--ink-2); margin: 0 5px 5px 0; }
.affed-key .mono { color: var(--accent); }

/* ---- the actions, above the panes where the office's screen has them ----
   WORKSTREAM B: the acts toolbar is now the sticky translucent material styled in the editor
   chrome block (~line 4280). This older block keeps only the pieces that block does not restate
   — the spacer, the status and the editing chip. */
.affed-actions .affed-spacer { flex: 1; }
.affed-status { font-size: 11.5px; color: var(--ink-3); }
.affed-status.dirty { color: var(--warn, var(--ink-2)); font-weight: 600; }
.affed-editing { font-size: 11px; padding: 2px 8px; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent);
  border: 1px solid var(--accent-line); }

/* ---- narrow screens: the panes stack, document first ---- */
@media (max-width: 1100px) {
  .affed { grid-template-columns: minmax(0, 1fr); }
  .affed-left, .affed-right { border: 0; border-top: 1px solid var(--line); }
  .affed-mid { order: -1; }
  .affed-sheet { padding: 28px 22px; font-size: 13px; }
}

/* ================================================================================================
   THE TRANSLATIONS WORKFLOW'S SCREEN. Its own block at the end, so it cannot perturb a rule above
   it — a second agent is working the Hague surface in this file's other sections.
   ================================================================================================ */
.tr-form { display: flex; flex-direction: column; gap: var(--s3); margin: var(--s3) 0; }
.tr-field { display: flex; flex-direction: column; gap: 6px; }
.tr-field > span { font-size: 13px; font-weight: 600; color: var(--ink-2); }
.tr-field > span em { font-weight: 400; color: var(--ink-3); font-style: normal; }
.tr-field input, .tr-field textarea {
  font: inherit; color: var(--ink); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-md); padding: 8px 10px; width: 100%;
  max-width: 62ch;
}
.tr-field textarea { resize: vertical; min-height: 72px; }
.tr-field input:focus, .tr-field textarea:focus { outline: 2px solid var(--accent-line); outline-offset: 1px; }

.tr-block { margin: var(--s4) 0 0; padding: var(--s3); border: 1px solid var(--line);
  border-radius: var(--r-lg); background: var(--surface); }
.tr-block h4 { margin: 0 0 8px; font-size: 13px; font-weight: 660; }
.tr-note { margin: 0; font-size: 12.5px; color: var(--ink-2); max-width: 82ch; }
.tr-note.ok { color: var(--ok, var(--accent)); font-weight: 560; }
.tr-warn { margin: 6px 0 0; font-size: 12.5px; color: var(--warn, #9a6b00); }
.tr-missing { color: var(--warn, #9a6b00); font-style: italic; }
.tr-src { font-size: 11.5px; color: var(--ink-3); }
.tr-list { margin: 6px 0 0; padding-left: 18px; font-size: 12.5px; color: var(--ink-2); }
.tr-list li { margin: 3px 0; }
.tr-refusals { margin: var(--s3) 0 0; padding-left: 18px; font-size: 12.5px; color: var(--ink-2); }
.tr-refusals li { margin: 4px 0; }

.tr-table { width: 100%; border-collapse: collapse; font-size: 12.5px; margin: var(--s2) 0 0; }
.tr-table th { text-align: left; font-weight: 620; color: var(--ink-2); font-size: 11.5px;
  text-transform: uppercase; letter-spacing: 0.04em; padding: 4px 8px 6px; border-bottom: 1px solid var(--line); }
.tr-table td { padding: 6px 8px; border-bottom: 1px solid var(--line); vertical-align: top; }
.tr-table code { font-size: 12px; color: var(--ink-2); }

/* ============================================================ APPENDED: the confirm layer above a page

   MEASURED DEFECT: pressing Remove in the Directory asked for confirmation, and the confirm was
   INVISIBLE until you left the page.

   Cause: `.modal-wrap` is `position: fixed; inset: 0; z-index: 60` and `.dir-overlay` is ALSO
   `z-index: 60`. Equal z-index means DOM ORDER decides, and `#dir-overlay` is appended to <body>
   at runtime - AFTER `#modal-wrap` (index.html line 419) - so the directory page painted over its
   own confirmation. The confirm was really there and really focused; it just had a full-screen
   panel in front of it. Pressing Escape or Cancel then "made it appear", because those closed the
   page behind it.

   Every other user of confirmDialog is inside a modal or the page flow, so none of them hit this.

   THE FIX IS SCOPED RATHER THAN GLOBAL. A hidden modal paints nothing, so raising its z-index can
   only affect a confirm that is actually open. `.src-overlay` (the provenance view, z-index 60) is
   included because it has the identical 60-vs-60 collision and is likewise appended to <body> at
   runtime; raising it unconditionally would be the same defect waiting for the next confirm opened
   from there. `.modal-wrap` is deliberately NOT raised globally, so that surfaces which do not need
   it keep exactly the layer they have always had.

   `:has()` is supported in every browser this console runs in; without support the rule simply
   does not apply and the behaviour is exactly as before. */
body:has(.dir-overlay:not([hidden])) #modal-wrap,
body:has(.src-overlay:not([hidden])) #modal-wrap {
  z-index: 100;
}


/* ================================================================================================
   THE CAPABILITY PANEL (E3.1) — APPENDED, never inserted mid-file.

   Every selector below is namespaced `#access-overlay` or `.acc-`, so this block cannot restyle
   anything that already exists. It is deliberately self-contained: the panel is an additive
   surface and a sibling session owns `app.css`'s other regions.

   Two things the layout has to get right, both measured elsewhere in this repo:
     * the panel can be TALL (22 capability rows + a 15-entry legend), so it scrolls INSIDE the
       overlay rather than growing the page — `overflow: hidden` on the shell plus one scrolling
       body is the pattern `.form-overlay` already uses;
     * `.acc-key` and `.acc-kind` are `mono` and can be long, so they wrap rather than push the
       row wider than its box on a phone.
   ================================================================================================ */
#access-overlay .acc-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3);
  flex-wrap: wrap; margin-bottom: var(--s4);
  padding-bottom: var(--s3); border-bottom: 1px solid var(--line, #e3e5e8);
}
#access-overlay .acc-head > span:first-child { font-size: 14px; font-weight: 640; }
#access-overlay .acc-counts { font-size: 12px; color: var(--ink-4); }

#access-overlay .acc-banner {
  margin: 0 0 var(--s4); padding: var(--s3) var(--s4); border-radius: 8px;
  background: var(--danger-soft); color: var(--danger, #a3261a); font-size: 13px;
  overflow-wrap: anywhere;
}

#access-overlay .acc-rows { display: grid; gap: var(--s2); }
#access-overlay .acc-row {
  padding: var(--s3) var(--s4); border-radius: 8px; background: var(--sunken, #f6f7f9);
  min-width: 0;
}
#access-overlay .acc-row[data-allowed="no"] { background: var(--sunken, #f6f7f9); }
#access-overlay .acc-row-head {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; min-width: 0;
}
#access-overlay .acc-label { font-size: 14px; font-weight: 640; overflow-wrap: anywhere; }
#access-overlay .acc-row .acc-key {
  font-size: 11px; color: var(--ink-4); overflow-wrap: anywhere; margin-left: auto;
}
#access-overlay .acc-pill {
  flex: none; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  padding: 2px 7px; border-radius: 999px; border: 1px solid var(--line, #e3e5e8);
}
#access-overlay .acc-pill.on { border-color: var(--ok-line, #6f9c7f); background: var(--ok-soft);
                              color: var(--ok, #1f6b3c); }
#access-overlay .acc-pill.off { color: var(--ink-4); }
#access-overlay .acc-reason {
  margin: var(--s2) 0 0; font-size: 13px; color: var(--ink-3); overflow-wrap: anywhere;
}
#access-overlay .acc-reason.acc-held { color: var(--ink-4); }
#access-overlay .acc-needs {
  margin: 6px 0 0; font-size: 11px; color: var(--ink-4); overflow-wrap: anywhere;
}

#access-overlay .acc-legend { margin-top: var(--s5); }
#access-overlay .acc-legend h4 {
  margin: 0 0 var(--s3); font-size: 13px; font-weight: 640; color: var(--ink-3);
}
#access-overlay .acc-legend-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s2); }
#access-overlay .acc-legend-item {
  font-size: 12px; color: var(--ink-3); overflow-wrap: anywhere;
  padding-left: 0;
}
#access-overlay .acc-legend-item[data-reached="no"] { color: var(--ink-4); }
#access-overlay .acc-kind { color: var(--ink); }
#access-overlay .acc-legend-ex {
  margin: 4px 0 0; font-size: 12px; color: var(--ink-4); overflow-wrap: anywhere;
}

/* A phone gets the same rows in one column; nothing here needs a second rule set, because the
   layout is already a single column of grid rows. The only thing the narrow width changes is the
   key, which stops being pushed to the right when there is no room to push it to. */
@media (max-width: 720px) {
  #access-overlay .acc-row { padding: var(--s3); }
  #access-overlay .acc-row .acc-key { margin-left: 0; flex-basis: 100%; }
}

/* ================================================================================================
   WHO HOLDS THIS JOB (E4.1, per D3) — APPENDED, never inserted mid-file.

   The access-control board's rule for this shared file is append-only, so every rule below is
   scoped to the elements `web/owner.js` creates (`#owner-strip`, `.owner-mini`, `.owner-tabnote`)
   and nothing above this line is changed. The four states carry a severity the SERVER states
   (`ok` / `warn` / `bad`), so the colour follows the module's reading rather than a rule typed
   here: a holder whose account has been deleted reads as a fault, and a job nobody holds reads as
   something to attend to — not as the same grey as everything else.
   ================================================================================================ */

.owner-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s2, 6px) var(--s3, 8px);
  padding: 6px 0 8px;
  margin: 0 0 var(--s3, 8px);
  border-bottom: 1px dashed var(--line, #ddd);
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-2, #555);
}

.owner-chip {
  flex: none;
  font-family: var(--mono, monospace);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.01em;
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid currentColor;
  white-space: nowrap;
}
.owner-chip[data-sev="ok"]   { color: var(--ok, #1a7f37); }
.owner-chip[data-sev="warn"] { color: var(--warn, #8a5a00); }
.owner-chip[data-sev="bad"]  { color: var(--danger, #b42318); }

.owner-strip .owner-reason { min-width: 0; overflow-wrap: anywhere; }
.owner-strip .owner-src,
.owner-strip .owner-same,
.owner-strip .owner-none {
  color: var(--ink-4, #888);
  font-family: var(--mono, monospace);
  font-size: 11px;
}
.owner-strip .owner-split { color: var(--ink, #111); font-weight: 600; }
.owner-strip .owner-why {
  flex-basis: 100%;
  color: var(--ink-4, #888);
  overflow-wrap: anywhere;
}
.owner-strip .owner-flag {
  flex: none;
  color: var(--danger, #b42318);
  font-size: 11px;
}

/* the one-line chip on a rail row and on a card */
.owner-mini {
  font-family: var(--mono, monospace);
  font-size: 11px;
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid currentColor;
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.owner-mini[data-sev="ok"]   { color: var(--ok, #1a7f37); }
.owner-mini[data-sev="warn"] { color: var(--warn, #8a5a00); }
.owner-mini[data-sev="bad"]  { color: var(--danger, #b42318); }

/* the tab-level line: how many of the jobs on this tab are not shown as anyone's */
.owner-tabnote {
  margin: 0 0 var(--s4, 12px);
  padding: 8px 10px;
  border-left: 3px solid var(--warn, #8a5a00);
  background: var(--hover, #f6f6f6);
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink-2, #555);
}

@media (max-width: 720px) {
  .owner-strip { padding: 4px 0 6px; }
  .owner-strip .owner-why { display: none; }   /* the chip keeps the reading; the why is on it */
  .owner-mini { max-width: 14ch; }
}

/* THE REASON LINE ON A PHONE. `.owner-why` is the module's full sentence ("the record names 'sara'
   as the holder but no account with the identifier 'sara' exists any more — this job is FLAGGED,
   not cleared …"). On a desk there is room for it under the chip; at 390px it is a WALL of text
   above the step-1 card the operator came to use, and the reading itself — the chip, the role and
   the second fact — is already on the line above it. This is an APPENDED override, so the base
   rules above are untouched; measured on the rendered phone screenshot, where the paragraph ran
   longer than the intake card beneath it. */
@media (max-width: 720px) {
  .owner-strip .owner-why {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
}

/* ================================================================================================
   THE FRONT DOOR AND THE SETTINGS SHELL (E1.5 / E5.6 / E5.2) — APPENDED, never inserted mid-file.

   Every selector below is namespaced `#settings-overlay` or `.st-`, so no existing rule can be
   restyled by this block. `web/capability_panel.js` already owns `#access-overlay` and the whole
   `.acc-*` family; this screen deliberately shares only the console's own `.form-overlay` and
   `.btn`, so it belongs to the same shell instead of inventing a second one.

   The widths are set in `em`/`px` against the console's own scale, and NOTHING here is gated on
   `hover`/`pointer`: this desk reports `hover: none`, so a pointer query would leak onto it. The one
   media query is the phone geometry the rest of the console uses (720px), and the rows are allowed
   to WRAP rather than clip — a clipped label is worse than a wrapped one.
   ================================================================================================ */

#settings-overlay .form-overlay-body {
  padding: 0;
  /* THE PANEL FILLS THE SHELL — ONE RECTANGLE, NOT A NARROW COLUMN UNDER A WIDE BAR.
     (Kazim, 2026-09-25: «it should be like a pop up yes but like a proper horizontal same ratio as
     the browser tab and almost as big as the whole page».)

     WHAT WAS WRONG. The shell is `display: flex; flex-direction: column`, so `.form-overlay-head`
     STRETCHES to the shell's full width while the body carried a `max-width`. MEASURED at his own
     2541x1285: head 2488x71 at x=26, body 1078x1161 at x=732. A full-width title bar floating over
     a narrow column is what «cooked» was describing — the two halves of one panel were two
     different shapes. The head's stretch is correct; the body's clamp was the odd one, and it is
     gone. Both now share the shell's width, so the panel reads as ONE landscape slab.

     THE SIZE IS THE SHELL'S OWN PADDING BOX: `inset: 0` less the shell's 24px gutter, i.e. the page
     minus a thin margin on every side — «almost as big as the whole page». Because the panel tracks
     the viewport in both axes, its shape IS the browser tab's shape at every window size, which is
     the other half of the ask. No `max-width` here on purpose: any cap would reintroduce the narrow
     column on a wide desk. Text does not run to the panel's edge — the content inside carries its
     own measure (`.st-section`), which is where a reading width belongs. */
  max-width: none;
  width: 100%;
  box-sizing: border-box;
}

/* THE PANEL IS CENTRED; THE SCRIM STILL COVERS THE SCREEN (fixed 2026-09-24, Kazim: «the settings
   and account settings still in mobile type mode»).

   WHAT WAS WRONG, and why the two earlier attempts at this did not fix it. `#settings-overlay` is
   the overlay ROOT: it is the element that paints the dimming scrim (`background: rgba(20,23,27,.55)`)
   and it is the element `inset: 0` stretches over the viewport. Narrowing the ROOT with a
   `max-width` therefore narrows the SCRIM WITH IT. MEASURED at the operator's own 2544x1291: the
   root ran x 733..1811 and every desk pixel beside it read (28,32,36) — the console's own dark
   ground, at FULL brightness — while the same reading inside the panel was also (28,32,36). The
   panel was a full-height column standing on an undimmed desk: precisely a phone sheet, which is
   what the operator kept calling it. My first two fixes were both real defects fixed (a 21em clamp
   on the shared `#ac-body` host, then a missing `margin: auto`), and neither was THE defect,
   because both treated the root's width as the panel's width.

   THE CONSOLE ALREADY HAS THIS RIGHT. `#notify-overlay` and `.mc-who-overlay` keep the root at
   full viewport and put the width on the INNER element (`.mc-who-overlay .form-overlay-body
   { max-width: 640px; margin: 0 auto; }`), so the scrim covers the whole screen and only the panel
   is narrow. This rule now follows that proven pattern: the root keeps `inset: 0` and NO max-width,
   and the width rides on the body — one pattern in the console, not two.

   The phone rules below are unchanged: at phone geometry the body is unclamped again and the panel
   becomes the page, which is the intended phone behaviour. */
#settings-overlay.st-overlay { max-width: none; }

/* THE FIRST SCREEN EVERY OPERATOR SEES. It sat at `8vh auto 0` — a form floating in the top
   third with dead space below it, on the one surface with nothing else on it. The overlay owns
   the whole screen (`body.signed-out .st-overlay`), so the login centres in it: a single column,
   optically centred (a touch above true centre, where a heading sits well), with the console's
   own mark above the lede so the screen opens with the thing it belongs to. */
.st-login {
  max-width: 21em;
  margin: 0 auto;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 1.2em;
  box-sizing: border-box;
}

/* THE LOGIN SIZES TO ITS CONTAINER, NOT THE VIEWPORT — MEASURED (Kazim, 2026-09-24):
   the sign-in renders INSIDE the settings overlay (head + scrollable body), and
   `min-height: 100dvh` demanded 900px for a 713px box — the overlay scrolled ~190px
   ("it is scrollable for some reason") and the card was NOT centred in what was on
   screen (its centre sat 187px above the clip). `100%` of the overlay body is the
   honest box; the body is a flex column so the auto margins centre without overflow. */
.st-overlay:not([hidden]) > .form-overlay-body {
  display: flex;
  flex-direction: column;
}
.st-overlay:not([hidden]) > .form-overlay-body > #ac-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  margin: 0 auto;
  width: 100%;
  box-sizing: border-box;
}
/* THE 21em CARD IS THE SIGN-IN'S, NOT THE SHELL'S (measured 2026-09-24, Kazim: «its display
   mobile style one but on desktop look»). `#ac-body` is the ONE host every view draws into —
   the login, the four Settings sections, and the account view — so the unqualified 21em clamp
   squeezed the whole shell into a narrow centred column on a desktop: the Settings tabs wrapped
   onto two rows and each section read as a phone sheet. The clamp now rides on the login's own
   wrapper: when `.st-login` is the content, the card keeps its measured 21em; any other view
   sizes to the shell (980px settings / 720px account, set by their own rules). `:has()` is the
   same selector this file already uses for the code-step (line ~4911) and the desk shell. */
.st-overlay:not([hidden]) > .form-overlay-body:has(> #ac-body > .st-login) > #ac-body {
  max-width: 21em;
}
/* THE CODE STEP JOINS THE CARD, IT DOES NOT QUEUE BELOW IT. `#stwofa` is appended after
   the login form inside `#ac-body`; with the form's full-height centring still in force
   the panel landed ~340px below it at the screen's foot (Kazim: "the gap is crazy and
   it's like at the bottom of the screen"). With the panel up, the centred-height demand
   is dropped, the suspended form sits compact at top, and the code step follows it at a
   conversational gap — one flow, top to bottom, nothing pushed off-screen. */
.st-overlay:not([hidden]) > .form-overlay-body:has(#stwofa:not([hidden])) > #ac-body,
.st-overlay:not([hidden]) > .form-overlay-body:has(#stwofa:not([hidden])) .st-login {
  min-height: 0;
  flex: 0 0 auto;
  justify-content: flex-start;
}
.st-overlay:not([hidden]) > .form-overlay-body:has(#stwofa:not([hidden])) > #ac-body {
  gap: 1.6em;
  padding-top: 1.6em;
}
/* the code panel speaks WITH the suspended form: same column, its lede as the active
   heading, and a hairline separator marking the step change — not a floating panel
   marooned in empty space. */
#stwofa {
  padding-top: 1.4em;
  border-top: 1px solid var(--line);
}

/* The mark, drawn by `renderLogin` before the lede: the same rosette the bar carries, so the
   signed-out screen is unmistakably this console and not a bare form. `.mark-rose`'s base rule
   pins 26px for the bar; the login's mark is its own size here, full opacity — it is the heading
   of this screen, not a toolbar glyph. */
.st-login-mark { margin: 0 0 1.1em; color: var(--accent); }
.st-login-mark .mark-rose { width: 34px; height: 34px; display: block; opacity: 1; }

/* THE LEDE IS THE HEADING. It was a 1.05em paragraph under nothing; as the first line of a
   centred screen it is the heading, and headings get size and tight tracking, not opacity. */
.st-login-lede {
  margin: 0 0 1.3em;
  font-size: 1.3em;
  font-weight: 680;
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--ink);
}

.st-login-form { display: flex; flex-direction: column; gap: 0.9em; align-items: stretch; }

.st-field { display: flex; flex-direction: column; gap: 0.35em; }

.st-field-label { font-size: 0.85em; opacity: 0.75; }

.st-input {
  font: inherit;
  padding: 0.55em 0.7em;
  min-width: 0;
  width: 100%;
  box-sizing: border-box;
  border-radius: 6px;
  border: 1px solid var(--line, currentColor);
  background: var(--raised, transparent);
  color: inherit;
}

/* The one action on this screen carries the primary weight: it is the only solid-accent control
   here, the same treatment "Create Hague" gets on the desk — a ghost button under a form read as
   a secondary action and left the screen without a visual anchor. */
.st-login-submit {
  align-self: stretch;
  min-height: 36px;
  margin-top: 0.2em;
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 600;
}
.st-login-submit:hover:not(:disabled) { background: var(--accent-hi); border-color: var(--accent-hi); }

.st-login-message {
  margin: 0.2em 0 0;
  padding: 0.6em 0.8em;
  border-left: 3px solid var(--fault, #b4463a);
  background: var(--sunken, transparent);
  border-radius: 4px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.st-banner {
  margin: 0.6em 0;
  padding: 0.7em 0.9em;
  border-left: 3px solid var(--fault, #b4463a);
  background: var(--sunken, transparent);
  border-radius: 4px;
  overflow-wrap: anywhere;
}

/* ---- the Settings tabs. A horizontal row of labels WRAPS at narrow widths rather than clipping
   its last word, which is the measured failure on this console's other tab strips. */
.st-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35em;
  padding: 0.8em 1.2em 0.4em;
  border-bottom: 1px solid var(--line, currentColor);
  /* THE TAB STRIP SHARES THE CONTENT'S MEASURE (Kazim, 2026-09-25: «it should be like a pop up yes
     but like a proper horizontal same ratio as the browser tab and almost as big as the whole
     page»). The panel is page-sized now, so a strip that ran the panel's full width would put the
     tabs at the far left while the section content sat centred 600px in — two things that belong to
     one column reading as two unrelated ones. The strip carries the SAME measure as the section
     below it and centres with it, so the tabs and the content share one left edge, and the border
     under the strip spans that measure — the column's own rule, not the panel's. */
  /* THE STRIP'S OWN SIDE PADDING IS ZERO ON A DESK, and that is what makes the alignment exact:
     the section's inner column is 72rem centred inside the section's 1.2em padding, so its content
     starts 1.2em in from the column's box. If the strip also carried 1.2em inside a 72rem box, the
     first tab would sit one padding further right than the section's first line (measured: 658 vs
     637). With no side padding and the same 72rem, the strip's content edge IS the section's content
     edge — first tab and first line share one left edge, and the border under the strip spans exactly
     the column's measure. The phone rules further down restore the side padding, where the panel is
     the page and there is no column to align with. */
  width: 100%;
  max-width: 72rem;
  margin-left: auto;
  margin-right: auto;
  padding-left: 0;
  padding-right: 0;
  /* `border-box`, NOT `content-box`: `content-box` ADDS the padding to the 100% width, and at 390px
     the strip drew 412px wide — caught by `verify_settings_shell_e56.py` ("nothing on the page is
     wider than the viewport"). Nothing may exceed the viewport. */
  box-sizing: border-box;
}

.st-tab {
  font: inherit;
  padding: 0.4em 0.8em;
  min-height: 32px;
  border-radius: 6px 6px 0 0;
  border: 1px solid transparent;
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.st-tab[aria-selected="true"] {
  border-color: var(--line, currentColor);
  border-bottom-color: transparent;
  background: var(--raised, transparent);
}

.st-section { padding: 1em 1.2em 2em; }

/* THE SECTION'S CONTENT MEASURE INSIDE A PAGE-SIZED PANEL (Kazim, 2026-09-25: «it should be like a
   pop up yes but like a proper horizontal same ratio as the browser tab and almost as big as the
   whole page»). The panel now fills the shell, so something inside must hold the reading measure or
   a sentence of the console's own prose runs the full width of a 2500px screen. A `max-width` on
   the section's inner column does that WITHOUT shrinking the panel: the slab stays page-sized and
   the text keeps a readable line. 72rem (~1152px at the console root, ~1267 under the 1.1 zoom) is
   deliberately WIDER than a prose column: this surface is dense product UI — cards, field rows and
   controls — and `operate` mode's rule is that data and compact UI run denser than prose while a
   real paragraph still lands in the 65–75ch band. The column is centred so the panel's own mass
   reads symmetrically. */
.st-section > * { max-width: 72rem; margin-left: auto; margin-right: auto; }
/* VERY WIDE DESKS: THE CONTENT GROWS WITH THE PANEL (Kazim, 2026-09-25: «it should be like a pop up
   yes but like a proper horizontal same ratio as the browser tab and almost as big as the whole
   page»). At his 2541px window the 72rem column used 50% of the panel — a page-sized slab holding a
   ribbon, with 610px of dead gutter on either side. This step is a STEP, not fluid type: `operate`
   mode's rule is a fixed rem scale, so the measure jumps once at a width where the extra room is
   real, and the account cards tile into two columns inside it. The tabs carry the same measure (see
   `.st-tabs`) so the alignment the column depends on is preserved at this width too. */
@media (min-width: 1700px) {
  .st-section > *, .st-tabs { max-width: 104rem; }
}
@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
  /* ON A PHONE THE PANEL IS THE PAGE and there is no spare width to centre a column in. */
  .st-section > * { max-width: none; }
}

/* A PERSON'S OWN ACCOUNT, BESIDE THE SECTION TABS AND NOT ONE OF THEM. It sits at the far END of the
   same strip so it reads as part of the same menu, but it carries its own class — the four `.st-tab`s
   are the server's catalogue and a check counts them, so this must not be one. `margin-left: auto`
   pushes it to the right, away from the administrative sections, because it is a different kind of
   thing: an employee's own account is not an administrative section at all. */
.st-own-entry {
  font: inherit;
  font-size: 0.9em;
  margin-left: auto;
  padding: 0.4em 0.8em;
  min-height: 32px;
  border-radius: 6px 6px 0 0;
  border: 1px solid var(--line, currentColor);
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.st-own-entry[data-selected="true"] {
  background: var(--raised, transparent);
  border-bottom-color: transparent;
}

@media (max-width: 640px) {
  /* ON A PHONE THE STRIP WRAPS AND THE ENTRY TAKES ITS OWN FULL-WIDTH ROW: at 390px a right-aligned
     control beside four tabs is a squeezed row with the label clipped, and `margin-left: auto` on a
     wrapped flex line leaves it against the right edge with dead space beside it. */
  .st-own-entry {
    margin-left: 0;
    flex-basis: 100%;
    width: 100%;
    min-height: 44px;
  }
}

.st-note { margin: 0.4em 0; opacity: 0.8; overflow-wrap: anywhere; }

.st-team-rows { display: flex; flex-direction: column; gap: 0.8em; margin: 1em 0; }

/* ON A WIDE DESK THE ACCOUNT CARDS USE THE PANEL'S WIDTH (Kazim, 2026-09-25: «it should be like a
   pop up yes but like a proper horizontal same ratio as the browser tab and almost as big as the
   whole page»). The panel is page-sized now, and one card per row left ~610px of dead gutter on
   either side at his 2541px window — a page-sized panel holding a ribbon. `operate` mode's rule for
   dense product UI is to use the width; cards are independent records (one per account), so they
   tile. AUTO-FIT rather than a fixed count, so the number of columns follows the panel: one on a
   phone, two or three on a desk. The floor is deliberately generous (34rem) because each card holds
   a two-factor block with its own explanation paragraphs — below that they wrap badly. */
@media (min-width: 1200px) {
  .st-team-rows {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(34rem, 1fr));
    align-items: start;
  }
}

.st-team-row {
  border: 1px solid var(--line, currentColor);
  border-radius: 8px;
  padding: 0.8em 1em;
  background: var(--surface, transparent);
  /* A flex column shrinks its children; this row's children are blocks, so the row itself must be
     allowed to grow or a locked row (five refusal paragraphs) collapses to a sliver. */
  flex: 0 0 auto;
}

.st-team-row-locked { background: var(--sunken, transparent); }

.st-team-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5em 0.8em;
}

.st-team-name { font-weight: 600; }

.st-team-key { opacity: 0.7; overflow-wrap: anywhere; }

.st-chip {
  font-size: 0.8em;
  padding: 0.15em 0.5em;
  border-radius: 10px;
  border: 1px solid var(--line, currentColor);
}

.st-team-facts { margin: 0.5em 0 0; opacity: 0.8; overflow-wrap: anywhere; }

.st-team-note { margin: 0.4em 0 0; opacity: 0.85; overflow-wrap: anywhere; }

.st-team-controls { display: flex; flex-wrap: wrap; gap: 0.5em; margin: 0.7em 0 0; }

.st-team-controls .btn { min-height: 32px; }

/* ---- THE REFUSALS. A withheld act is a PARAGRAPH, never a greyed control (R14), and it is set
   apart from the controls so a reader can see at a glance that nothing here is pressable. */
.st-team-refusals { margin: 0.6em 0 0; }

.st-refusal-sentence {
  margin: 0.3em 0;
  padding: 0.45em 0.7em;
  /* a refusal is a paragraph, not a severity: no bar, the words carry it */
  border-left: 3px solid transparent;
  opacity: 0.85;
  overflow-wrap: anywhere;
}

.st-panel {
  margin: 0.8em 0 0;
  padding: 0.8em 0.9em;
  border: 1px solid var(--line, currentColor);
  border-radius: 6px;
  background: var(--raised, transparent);
}

.st-panel-title { margin: 0 0 0.4em; font-size: 0.95em; }

.st-panel-note { margin: 0.3em 0 0.7em; opacity: 0.8; overflow-wrap: anywhere; }

.st-panel .st-input { max-width: 28em; }

.st-create-block { margin: 1em 0; }

.st-create { margin: 0.8em 0 0; }

.st-create-form { display: flex; flex-direction: column; gap: 0.7em; max-width: 28em; }

.st-create-form .btn { align-self: flex-start; }

.st-act-report {
  margin: 0 0 1em;
  padding: 0.7em 0.9em;
  border-left: 3px solid transparent;  /* the outcome states below colour the bar */
  border-radius: 4px;
  overflow-wrap: anywhere;
}

.st-act-report[data-ok="yes"] { border-left-color: var(--settled, #3c7a4a); }

.st-act-report[data-ok="no"] { border-left-color: var(--fault, #b4463a); }

.st-act-ok { font-weight: 600; }

.st-act-fail { font-weight: 600; }

.st-act-fail-said { opacity: 0.9; }

.st-danger { border-color: var(--fault, #b4463a); }

/* The desk behind the overlay is not reachable while it is up. `st-locked` is set on <body> by the
   overlay and removed when it closes, so a stray scroll cannot move the page under it. */
body.st-locked { overflow: hidden; }

/* ---- phones. One site, two rule sets, and the split is by GEOMETRY only. At the phone width the
   panel is the page: the overlay becomes a full-height sheet, and the rows wrap rather than clip. */
@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
  /* ON A PHONE THE PANEL IS THE PAGE, and that is now about the BODY: the root spans the viewport
     in every width (it is the scrim), so the phone rule is what lifts the body's 980px clamp and
     lets the panel run edge to edge. The account view's 720px body clamp is lifted here too. */
  #settings-overlay.st-overlay {
    max-width: none;
    inset: 0;
    border-radius: 0;
  }
  #settings-overlay .form-overlay-body,
  #settings-overlay.st-own-view .form-overlay-body { max-width: none; width: 100%; margin: 0; }
  /* the login stays centred on a phone too: the desktop rule's min-height flexbox holds, only
     the outer padding changes (the old `margin-top: 4vh` pushed the form back to the top) */
  .st-login { margin: 0 auto; padding: 0 1em; }
  .st-tabs { padding: 0.6em 0.8em 0.3em; }
  .st-section { padding: 0.8em 0.9em 2em; }
  .st-team-row { padding: 0.7em 0.8em; }
  .st-team-head { gap: 0.35em 0.5em; }
  .st-team-controls { gap: 0.4em; }
  .st-panel .st-input, .st-create-form { max-width: none; }
}

/* ================================================================================================
   THE DEVELOPER'S ROW, AND THE CONTROLS IT DOES NOT HAVE (E0.5 / D5 / R14 / R15)

   APPENDED, never inserted (the board's rule for this file). All selectors are namespaced
   `.acc-dev*`, so nothing already in the console can be restyled by this block — the capability
   panel's own styles are `#access-overlay` / `.acc-*` and this adds ONE child element to it.

   WHAT IT DRESSES. The panel draws the developer's account as a FACT with no control for any of
   the five acts that land on it (D5: "NOT drawn as an editable row in an admin's list"), and it
   draws each refused act as a LINE rather than as a dead button, because a disabled button is a
   control that is drawn and cannot be used — the other half of the lie R14 rules out. The lines
   are deliberately quiet and left-bordered: they are statements of what is NOT offered, not
   actions, and they must not look like something to click.

   NO `button`/`input` SELECTOR APPEARS HERE AT ALL: `capability_panel.js` counts the controls
   inside this block off the DOM, and this file must not be the reason that count is non-zero. */
.acc-devrow {
  margin: 1em 0 0.4em;
  padding: 0.7em 0.8em;
  border: 1px solid var(--line, #2b3440);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.02);
}
.acc-devrow h4 {
  margin: 0 0 0.45em;
  font-size: 0.95em;
  letter-spacing: 0.02em;
}
.acc-devrow p { margin: 0.3em 0; }
.acc-devfacts { font-weight: 600; }
.acc-devnote { opacity: 0.85; }
.acc-devself { opacity: 0.85; font-style: italic; }
/* One line per act that is NOT drawn as a control. Left border = "this is a statement about an
   act", never an affordance: no pointer cursor, no hover state, no button role. */
.acc-devact {
  border-left: 3px solid var(--warn-line);  /* token, not a literal amber */
  padding: 0.2em 0 0.2em 0.6em;
  font-size: 0.88em;
  opacity: 0.9;
  cursor: default;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* ---- phones: the same geometry-only split the rest of this site uses. The row is the page width
   and the act lines wrap rather than scroll sideways (a horizontal scroller on a phone hides half
   of every line, and the act NAMES are the point). */
@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
  .acc-devrow { border-radius: 0; margin: 0.7em 0; padding: 0.6em 0.7em; }
  .acc-devact { font-size: 0.9em; padding-left: 0.5em; }
}

/* ================================================================================================
   WHO IS SIGNED IN, AND SIGNING OUT (E5.1) — APPENDED, never inserted mid-file.

   Namespaced `#team-session` / `.mc-who*` / `#signout-*` / `.so-*`, so no rule above this line can
   be restyled by it and the two sessions appending to this file have no third writer in their own
   region.

   `#team-session` LIVES IN `.bar-tools`, BESIDE DIRECTORY AND NOTIFICATIONS. Its box is measured
   against the viewport rather than trusted. On a phone `.bar-tools` has only about 215px of room
   (measured in `bar-phone.js`: three controls already ran 131 -> 495 in a 390px viewport), and the
   chip's text is the OPERATOR'S OWN NAME, which has no length this file can assume — so the chip is
   given `max-width` and a truncating ellipsis rather than pushing the bar out, and on a phone it is
   narrowed further so the "More" button cannot be pushed off the edge. `min-width: 0` on the parts
   allowed to shrink is what makes a flex child actually shrink: without it a text node's own width
   is a floor and the control is what gets clipped.

   `#signout-dialog` IS A `.modal-wrap`-CLASS LAYER. Its own class is at `z-index: 60`, and the
   sign-out control is in the BAR (not inside a full-screen page), so nothing sits over it. The
   console's own rule about this — that two full-screen layers at the same `z-index` are ordered by
   DOM position, and a runtime-appended one wins — is why this dialog does not use the shared
   `#modal-wrap` NODE: that node is a sibling board's surface and `app.css` already lifts it over
   `.dir-overlay` and `.src-overlay` with a scoped `:has()` rule. A second user of that node would
   inherit that scoping for no reason; its own node at the same layer cannot collide with anything
   that was appended before it. */

#team-session {
  display: inline-flex; align-items: center; gap: 8px;
  max-width: 240px; min-width: 0; padding: 4px 10px;
  cursor: pointer; text-align: left;
}
#team-session[hidden] { display: none; }
#team-session .mc-who-face {
  font-weight: 640; font-size: 13px; color: var(--ink);
  max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#team-session .mc-who-sub {
  font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-3);
  padding-left: 8px; border-left: 1px solid var(--line-2); white-space: nowrap;
}
/* The chip is a STATE, not an action, and it must not read as a primary control: it keeps the
   console's own ghost look and adds the one thing that matters at a glance — who you are. */
#team-session:hover { background: var(--hover); }
#team-session:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* SIGNED OUT, THE CHIP IS NOT DRAWN. `body.signed-out` is E1.5's own class on <body> and the desk
   behind the login is hidden by `access-signedout.css`; a "nobody is signed in" chip floating on the
   login screen would be the console talking about a session to the person who has not got one. The
   element is NOT removed — `hidden` is what a check reads, and a removed element is
   indistinguishable from one that was never built. */
body.signed-out #team-session { display: none !important; }
body.signed-out #signout-open { display: none !important; }

/* THE PANEL THE CHIP OPENS. Reuses the console's own `.form-overlay` shell so it belongs to the
   same screen family; every inner class is namespaced. */
.mc-who-overlay { align-items: center; }
.mc-who-overlay .form-overlay-body { max-width: 640px; width: 100%; margin: 0 auto; }
#mc-who-body { padding: var(--s4); }
.mc-who-name { font-size: 20px; font-weight: 680; letter-spacing: -0.01em; color: var(--ink); }
.mc-who-role {
  display: inline-block; margin: 6px 0 var(--s4);
  font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--accent); background: var(--accent-soft);
  border: 1px solid var(--accent-line); border-radius: var(--r-sm); padding: 3px 9px;
}
.mc-who-row { display: flex; align-items: baseline; gap: var(--s3); padding: 7px 0;
              border-bottom: 1px solid var(--line); min-width: 0; }
.mc-who-k { flex: none; width: 118px; font-size: 11px; text-transform: uppercase;
            letter-spacing: 0.06em; color: var(--ink-3); }
.mc-who-v { flex: 1 1 auto; min-width: 0; font-size: 13px; color: var(--ink);
            overflow-wrap: anywhere; }
.mc-who-v[data-absent="yes"] { color: var(--ink-4); font-style: italic; }
.mc-who-note { margin: var(--s4) 0 0; font-size: 12px; color: var(--ink-3);
               line-height: 1.45; overflow-wrap: anywhere; }
.mc-who-said { padding: var(--s4); font-size: 13px; color: var(--ink-2);
               line-height: 1.5; overflow-wrap: anywhere; }

/* THE SIGN-OUT CONFIRMATION. Its card is the console's `.modal` look with the paragraphs spaced. */
#signout-dialog .so-card { max-width: 520px; width: 100%; }
#signout-dialog .so-who { margin: 0 0 var(--s3); font-size: 14px; font-weight: 620; color: var(--ink); }
#signout-dialog .so-what,
#signout-dialog .so-effect { margin: 0 0 var(--s3); font-size: 13px; color: var(--ink-2);
                             line-height: 1.5; overflow-wrap: anywhere; }
#signout-dialog .so-effect { color: var(--ink-3); }
#signout-dialog .so-said {
  margin: var(--s3) 0 0; padding: 10px 12px; font-size: 13px; line-height: 1.5;
  border-radius: var(--r-md); overflow-wrap: anywhere;
  border: 1px solid var(--line-2); background: var(--sunken); color: var(--ink-2);
}
#signout-dialog .so-said[hidden] { display: none; }
#signout-dialog .so-said[data-kind="ended"] { border-color: var(--ok-line); }
#signout-dialog .so-said[data-kind="refused"] { border-color: var(--amber, #c98a00); color: var(--ink); }

@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
  /* On a phone the chip shows the name only: the role is on the panel a press away, and the bar's
     own overflow sheet carries the whole control with its accessible name intact. */
  #team-session { max-width: 150px; padding: 4px 8px; }
  #team-session .mc-who-face { max-width: 118px; }
  #team-session .mc-who-sub { display: none; }
  .mc-who-k { width: 92px; }
  #mc-who-body { padding: var(--s3); }
}

/* ================================================================================================
   E5.1 — THE BAR FITS, WITH THE IDENTITY IN IT (APPENDED, scoped, and MEASURED).

   THE DEFECT, measured before this block existed, at 1440x900 on a signed-in desk:
       bar 1440x62      mark 392x63      (the mark's DESIGN height is 42)
       .mark-name  "Mission Control" rendered on TWO LINES and .bar-meta's "queue" sentence on
       THREE, with the identity chip and Sign out in the bar.
   AND THE ATTRIBUTION, which is the part worth recording: removing E5.1's two controls entirely
   (not hiding them — the sign-out module re-applies its own visibility, so a hidden node measures
   nothing) left the bar at **62px with the mark at 63px and "Mission Control" still on two lines**.
   So the desk's top bar had no slack BEFORE this card's controls arrived, and the card's own
   obligation is the bar that results WITH them.

   THE CAUSE, and it is one line of CSS one level in. `.mark-sub` ("Hague · Informal · Translations")
   carries `white-space: nowrap`, so its min-content width is a FLOOR: the mark cannot shrink below
   it, the flex line therefore refuses to shrink, and `.mark-name` absorbs the shortfall by wrapping
   onto a second line. Measured: keeping the slogan, the bar needs **1458px of a 1440px viewport**;
   without it, **1210px**.

   THE FIX IS THE ONE THE CONSOLE ALREADY MAKES FOR PHONES, in its own words: `.mark-sub { display:
   none; }  /* the tabs say it; the mark keeps its name */` — the same three workflow names are on
   the tab strip 100px below this bar, so the mark loses nothing a reader needs and keeps the name
   it is there for. Measured after: mark 392x63 -> 182x42 (its design size), bar right edge 1440
   with `scrollWidth - clientWidth` 0, nothing wider than the viewport, and the name on ONE line.

   SCOPED, NOT GLOBAL — AND THE SCOPE IS AN HONEST ONE. It applies only while `#team-session`
   carries `data-session="person"`, i.e. only in the state this card adds (a live session, so an
   identity is in the bar). A signed-out desk, and any build where `session_identity.js` did not
   load, keeps the base `app.css` rule EXACTLY as it was — the rule cannot half-apply and cannot
   reach a screen that has no identity strip. If the office would rather keep the slogan at desk
   and lose something else instead, this is the one block to change. */
body:has(#team-session[data-session="person"]) .bar .mark-name { white-space: nowrap; }
body:has(#team-session[data-session="person"]) .bar .mark-sub { display: none; }
/* The groups allowed to shrink. Without `min-width: 0` a flex child's min-content width is a floor
   and the shortfall is pushed out of the viewport instead — measured at 1458px on the first
   attempt, which is the same defect one level out. */
body:has(#team-session[data-session="person"]) .bar .bar-meta { min-width: 0; }
body:has(#team-session[data-session="person"]) .bar .bar-tools { min-width: 0; }

/* ---- phones: THE ROLE IS PART OF THE IDENTITY, AND THE SHEET ROW HAS ROOM FOR IT.
   MEASURED DEFECT, read off the render at 390x844: the phone's overflow sheet showed the identity
   row as "Ada Okafor" alone. On the desk the chip carries the role in a second, bordered span, and
   the phone rule above dropped that span to keep the chip narrow — but on a phone the chip lives in
   the overflow SHEET, where the row is the full page width, so there was nothing to be gained and
   the one fact the card is about (this operator's ROLE) was gone from the screen. A person on a
   phone with two tabs open could not tell an admin's session from an employee's.
   The span comes back, as one continuous label with a separator rather than a bordered box — the
   sheet's own row style, and no extra width worth measuring. */
@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
  #team-session { max-width: 190px; }
  #team-session .mc-who-face { max-width: none; }
  #team-session .mc-who-sub {
    display: inline; padding-left: 0; border-left: 0; color: var(--ink-2);
  }
  #team-session .mc-who-sub::before { content: "· "; }
  /* The sheet row is wider than the chip's own box, so the chip is allowed the row's width rather
     than being clamped to a bar-sized sliver. */
  .bar-sheet-row #team-session { max-width: 100%; }
}
/* =================================================================================================
   THE FEATURE BLOCKS PANEL (E3.2 / E3.3) — APPENDED ONLY, never inserted mid-file.

   Every selector is namespaced `#blocks-overlay` or `blk-`, so nothing existing can be restyled and
   nothing here can collide with `capability_panel.js`'s `.acc-*` or `access.js`'s `st-`. The only
   shared classes are the console's own `.form-overlay` and `.btn`, reused on purpose so this screen
   belongs to the same shell.
   ================================================================================================= */

#blocks-overlay .blk-head {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: baseline;
  padding: 4px 2px 10px;
  border-bottom: 1px solid var(--line, #2a2f3a);
}

#blocks-overlay .blk-can {
  color: var(--muted, #8b93a3);
  font-size: 12px;
}

#blocks-overlay .blk-banner {
  margin: 8px 0;
  padding: 8px 10px;
  border: 1px solid var(--warn, #a8763a);
  border-radius: 6px;
  background: rgba(168, 118, 58, 0.10);
}

#blocks-overlay h4 {
  margin: 16px 0 6px;
  font-size: 13px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--muted, #8b93a3);
}

#blocks-overlay .blk-force,
#blocks-overlay .blk-none {
  margin: 2px 0;
  font-size: 13px;
}

#blocks-overlay .blk-none {
  color: var(--muted, #8b93a3);
}

#blocks-overlay .blk-cell {
  border-top: 1px solid var(--line, #2a2f3a);
  padding: 8px 0;
}

#blocks-overlay .blk-role {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  padding: 4px 0;
}

#blocks-overlay .blk-rolehead {
  min-width: 260px;
  font-size: 12px;
  color: var(--muted, #8b93a3);
}

/* THE STOPPED STATE IS A PARAGRAPH, and it is given the width a paragraph needs — never squeezed
   onto one line with a control, because the reason is the whole point of the state. */
#blocks-overlay .blk-reason {
  flex: 1 1 100%;
  margin: 2px 0;
  font-size: 13px;
}

#blocks-overlay .blk-meta {
  flex: 1 1 100%;
  margin: 0;
  font-size: 11px;
  color: var(--muted, #8b93a3);
}

#blocks-overlay .blk-note {
  flex: 1 1 100%;
  margin: 0;
  font-size: 12px;
  color: var(--muted, #8b93a3);
}

#blocks-overlay .blk-said {
  margin-top: 14px;
  font-size: 12px;
  color: var(--muted, #8b93a3);
}

/* A PHONE GETS THE SAME PAGE, STACKED. The role head stops claiming a desk-sized column, so the
   reason is not pushed off the side of a 390px screen — the trap an `overflow: hidden` ancestor
   makes invisible while every field-level check passes. */
@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
  #blocks-overlay .blk-rolehead {
    min-width: 0;
    flex: 1 1 100%;
  }

  #blocks-overlay .blk-role {
    gap: 4px;
  }
}

/* ================================================================================================
   THE SHARING SURFACE (E4.3, per D2/D3) — APPENDED ONLY, every selector namespaced `.case-share*`.

   It dresses the block `web/case_share.js` inserts beside the holder strip and the one-line mark a
   rail row gets when a job has CHANGED HANDS. Nothing here restyles an existing rule: a base rule
   edited for one new screen is how a sibling card's layout breaks without its check noticing.
   ================================================================================================ */

.case-share {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 10px;
  margin: 0 0 10px;
  padding: 8px 12px;
  border: 1px solid var(--line, #d9dee5);
  border-radius: 8px;
  /* THE SURFACE IS THE THEME'S OWN, NOT A LIGHT-MODE FALLBACK.
     `var(--surface-2, #f7f9fb)` named a variable this stylesheet DOES NOT DEFINE — its surfaces are
     `--bg`, `--surface`, `--raised`, `--sunken` — so the fallback was taken in EVERY theme and the
     panel drew `#f7f9fb` (a near-white) with `#3c4753` text. In dark mode that is a pale slab
     sitting on a dark page, measured on the deployed console: a Translations job's "what this job
     offers you" panel and the approval strip below it both read as light-mode screens pasted into a
     dark console.
     `--surface` IS redefined by `[data-theme="dark"]`, so the panel now follows the theme. It is the
     right layer too: this is a panel INSIDE the stage, not a floating card, so it takes the surface
     rather than `--raised`. */
  background: var(--surface, #f7f9fb);
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink-2, #3c4753);
}

.case-share-head,
.case-share-holder {
  font-weight: 600;
  color: var(--ink, #1b2430);
}

.case-share[data-severity="warn"] {
  border-color: #d7c07a;
}

.case-share[data-severity="bad"],
.case-share[data-share-read="unknown"],
.case-share[data-share-read="unreadable"] {
  border-color: #c98b84;
}

.case-share-count,
.case-share-with,
.case-share-order,
.case-share-why {
  color: var(--ink-3, #5b6672);
}

/* THE ORDERING RULE IS A SENTENCE, NOT A WALL — it wraps on a narrow desk rather than pushing the
   block wider than the card it sits in. `min-width: 0` is what actually allows the wrap inside a
   flex row: without it a long unbroken token sets the row's floor. */
.case-share-count,
.case-share-with,
.case-share-order,
.case-share-acts,
.case-share-approval,
.case-share-flags {
  min-width: 0;
  overflow-wrap: anywhere;
}

.case-share-acts {
  flex: 1 1 100%;
  margin: 2px 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
}

.case-share-act[data-verdict="allowed"] {
  color: #1c6b3a;
}

.case-share-act[data-verdict="refused"],
.case-share-act[data-verdict="not-established"] {
  color: #8a4b46;
}

.case-share-approval {
  flex: 1 1 100%;
  margin: 2px 0 0;
  padding-top: 4px;
  border-top: 1px dashed var(--line, #d9dee5);
  color: var(--ink-3, #5b6672);
}

.case-share-history {
  flex: 1 1 100%;
  margin: 2px 0 0;
}

.case-share-history summary {
  cursor: pointer;
  color: var(--ink-2, #3c4753);
}

.case-share-history ol {
  margin: 6px 0 0;
  padding-left: 18px;
}

.case-share-row {
  margin: 2px 0;
}

.case-share-to {
  font-weight: 600;
}

.case-share-meta {
  margin-left: 6px;
  color: var(--ink-3, #5b6672);
}

/* THE RAIL MARK — one line, never a column, so a 390px row keeps its own layout.
   THE WARN TINT COMES FROM THE THEME, NOT FROM A LITERAL BEIGE. `#efe6d2` on `#6b5527` is a light
   warm chip that reads correctly on a light page and is a pale slab on a dark one — measured in
   dark mode on a Translations job, in the rail beside "Held by". `--warn-soft` / `--warn` are the
   palette's own warning pair and BOTH are redefined by `[data-theme="dark"]`, so the chip follows
   the theme instead of fighting it. The literals remain as fallbacks. */
.case-share-mini {
  display: inline-block;
  margin-left: 6px;
  padding: 0 5px;
  border-radius: 6px;
  background: var(--warn-soft, #efe6d2);
  color: var(--warn, #6b5527);
  font-size: 11px;
  line-height: 16px;
  white-space: nowrap;
}

@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
  /* A phone gets a drill-down, so the block is the page's width: it stacks rather than competing for
     a row, and the count/order sentences take the full width instead of forcing a sideways scroll. */
  .case-share {
    flex-direction: column;
    align-items: stretch;
    gap: 3px;
  }

  .case-share-acts {
    flex-direction: column;
    gap: 2px;
  }

  /* THE SEPARATOR IS HIDDEN WHERE THE BLOCK STACKS, so a phone reads one fact per line rather than
     a lone "·" between them. See `case_share.js` for the measurement. */
  .case-share > .case-share-sep,
  .case-share-acts > .case-share-sep {
    display: none;
  }

  .case-share-history ol {
    padding-left: 14px;
  }
}

/* ============================================================ THE SECOND STEP OF A SIGN-IN (E7.5)
   The code step's panel, drawn by `signin_twofa.js` INSIDE the login overlay's own body
   (`#ac-body`), under the login form. APPENDED ONLY, per this board's rule, and every selector is
   namespaced `#stwofa` so no existing rule can be restyled.

   WHY IT EXISTS AT ALL — MEASURED, in the browser: with no rule of its own the panel rendered with
   the browser's defaults, and at 1440px wide the field was 1440px wide (the field is `width: 100%`
   of a panel that had no box), the button OVERLAPPED the field's own border, and at 390px the panel
   bled past the screen's edges. The panel needs the same column, field and input dress the login
   form already uses — and `#stwofa` is a plain block, so nothing here inherits from `.st-login`. */
#stwofa {
  max-width: 30em;
  margin: 0 auto;
  padding: 1.4em 1.2em 0;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 0.9em;
  align-items: stretch;
}
#stwofa[hidden] { display: none; }
#stwofa .stwofa-lede { margin: 0; font-size: 1.05em; }
#stwofa .stwofa-again { align-self: flex-start; font-size: 0.9em; }
#stwofa .stwofa-until { margin: 0; font-size: 0.85em; opacity: 0.75; }
#stwofa .stwofa-message {
  margin: 0;
  padding: 0.6em 0.8em;
  border-left: 3px solid var(--danger-line, #b4463a);  /* the palette's danger line, not a stray hex */
  background: var(--danger-soft, rgba(180, 70, 58, 0.08));
}
/* A PROMPT IS NOT AN ERROR. `paint()` has one channel for every sentence, and the FIRST thing
   the panel says — "Enter the 6-digit code…" when the box is submitted empty — styled as
   danger, read as failure before anything was tried (seen on the 2026-09-24 screenshots).
   A quiet hint is the honest weight for "nothing entered yet"; real refusals keep the
   danger dress via `data-kind="error"` (set below by the same JS that paints them). */
#stwofa .stwofa-message:not([data-kind="error"]) {
  border-left-color: var(--line);
  background: var(--sunken);
  color: var(--ink-2, inherit);
}
#stwofa .stwofa-message[data-kind="error"] {
  border-left-color: var(--danger-line, #b4463a);
  background: var(--danger-soft, rgba(180, 70, 58, 0.08));
}
#stwofa .stwofa-submit {
  /* the 2FA code COMPLETES the sign-in the primary button above started — same solid-accent
     weight, so the two-step flow reads as one action at one weight, not two weights. */
  align-self: stretch;
  min-height: 36px;
  margin-top: 0.2em;
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 600;
}
#stwofa .stwofa-submit:hover:not(:disabled) { background: var(--accent-hi); border-color: var(--accent-hi); }
/* THE SUSPENDED PASSWORD FORM IS TAKEN OUT OF PLAY, NOT HIDDEN: the person must be able to see
   which account they typed while they enter the code, and a form that vanished would make the
   screen look like it belonged to nobody. */
#ac-login-form.stwofa-suspended { opacity: 0.45; }

/* ================================================================================================
   [E5.5] THE PHONES: THE LOGIN, SETTINGS, AND THE BLOCKED STATE — APPENDED, never inserted mid-file.

   ONE SITE, TWO RULE SETS, SPLIT BY GEOMETRY ONLY: `max-width: 720px`, or
   `max-width: 900px and max-height: 500px` for a landscape phone. NOTHING here is gated on
   `hover`/`pointer` — this desk reports `hover: none`, so a pointer query would leak onto the
   desktop. Measured before this block was written, on a scratch console at all eight sizes:

     * `#settings-overlay` measured 3,176px tall inside an 844px viewport, its own `overflow-y`
       resolved to `visible`, and the document could not scroll (`scrollTo(0, 500)` moved the page
       by 0px). Its content below the fold was reachable only by a trackpad two-finger swipe — on
       the device this card is about it did not scroll at all.
     * the Settings tabs measured 95px tall across two rows at 390px and 320px.
     * `Sign in` measured 35px tall and the password field 42px, at every phone width.

   WHAT THIS BLOCK DOES. `.form-overlay-body` is the overlay's OWN scroller (`overflow: auto`,
   `flex: 1`) and it already reaches every row — measured at 390x844: 2,895px of content in a
   709px box, `scrollTop` moving a full 500px when driven. So the phone does not need a second
   scroller; what it needs is for the shell to stop being a `position: fixed` box whose height the
   phone's own URL bar and safe areas can cut into. The overlay is anchored to the DOCUMENT on a
   phone (`inset: 0` with `height: auto`), so the page scrolls as one and the body's scroller still
   works on the desk. `position: fixed` on a phone is the trap here, and the console has already
   paid for it twice (see `bar-phone.css` and the `.wfbar` sticky notes).

   THE `@media (min-width: 2600px)` BLOCK IS **NOT** AT THE END OF THIS FILE, and the first draft of
   this comment said it was (it is at line ~4603, ahead of several other cards' appends) — corrected
   by READING the file rather than assuming, which is the same discipline the block's own prose asks
   for. WHAT MAKES THE APPEND SAFE IS SIMPLER AND DOES NOT DEPEND ON WHAT PRECEDES IT: a `@media`
   block does not inherit the query of the block above it, so the `(max-width: 720px)` here is
   evaluated on its own and can never fire on a desk. Nothing below this marker is anything but this
   card's rules, which is asserted by the check.
   ================================================================================================ */

/* ---- the login: the same field set, thumb-sized ----
   MEASURED before: `Sign in` 74x35px, the password field 357x42px. The rule is not a style
   preference — 35px is under the 44px short side every other control on this console is held to,
   and the login is the one screen a person reaches for a phone to use. Inputs go to 16px as well:
   below that iOS zooms the page on focus, which on this screen means the field the person is
   typing into is suddenly off-screen. */
@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
    .st-login { margin: 0 auto; padding: 0 1em; }  /* centred, not pushed to the top */
  .st-login-submit { align-self: stretch; min-height: 46px; }
  .st-input { min-height: 46px; font-size: 16px; }
  /* the shell is the page, not a floating box a phone can cut into */
  .form-overlay.st-overlay { position: absolute; min-height: 100%; }
  /* a tab strip is a row of tappable labels, so each one is a target and the strip wraps rather
     than clipping its last word (measured two rows at 390px either way — the difference is that
     every label is now reachable with a thumb) */
  .st-tabs { padding: 0.5em 0.7em 0.3em; gap: 0.3em; }
  .st-tab { min-height: 44px; }
}

/* ---- Settings: a DRILL-DOWN, not a sheet over the rail ----
   THE DECISION THIS CARD HAD TO TAKE, and it is recorded here because the geometry is what
   implements it. Settings REPLACES THE SCREEN on a phone: the whole viewport, and the console's
   own shell (bar, workflow tabs, rail) is not painted behind it. A sheet over the rail was the
   other option and it is wrong for this screen — Settings holds four sections and a row per
   account, and a sheet that must leave room for what it is covering is a sheet whose content is
   squeezed to keep a half-drawn desk visible at its edges. The overlay is the same DOM node on
   both devices; on a phone it owns the screen, and the desk is hidden under it.

   THE DESK IS HIDDEN FOR THE SAME REASON `access-signedout.css` HIDES IT WHEN SIGNED OUT:
   covering is a paint order, not a lock. `access.js` sets the SAME class it already sets for the
   login and clears it in `close()` — no JS is changed by this card, and the class already exists
   for exactly this purpose. The overlay itself is deliberately NOT in this list. */
@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
  body.st-locked > header,
  body.st-locked .desk,
  body.st-locked .wfbar,
  body.st-locked #stepper,
  body.st-locked .banner {
    display: none;
  }
  /* the overlay is now the only thing in flow: the bar's own height is gone with it, so the head
     row and the first section start at the top of the screen */
  #settings-overlay.st-overlay { border-radius: 0; max-width: none; padding: 0; }
  #settings-overlay .form-overlay-head { border-radius: 0; }
  #settings-overlay .form-overlay-body { border: 0; border-radius: 0; }
}

/* ================================================================================================
   THE OWNER STRIP'S FULL SENTENCE AT PHONE WIDTH — APPENDED BY E4.3 (t_3fbfb460), never inserted
   mid-file. It overrides ONE rule of E4.1's, in the direction that was MEASURED to be wrong.

   WHAT WAS MEASURED, on the rendered 390x844 capture (`tools/out/e43_share_phone.png`, looked at,
   not asserted on): E4.1's appended rule clamps `.owner-strip .owner-why` to TWO lines, and the
   strip's own reading is longer than that on a job whose holder was resolved from an account —

       the case's own record names 'sara' as its holder (Sara),
       account active — employee, resolved from the holder's…

   It reads as though the sentence had been cut off, which is the one thing a provenance line in
   this console must never look like (R2/R8: a value and the reason it was reached are read
   together, and a reason shown half-way is a reason that cannot be checked). The clamp was added
   for a real symptom — a job whose holder's ACCOUNT IS GONE carries a much longer flag sentence,
   and at 390px it was a wall of text above the step-1 card the operator came to use.

   SO THE WALL IS BOUNDED, NOT THE READING. Three lines, never two, and the tail is ellipsised by
   the clamp rather than the markup; a sentence that still does not fit is a longer reading than a
   phone screen holds rather than a value that was thrown away. This is an APPENDED override on a
   `@media` that already exists, so E4.1's base rules and the desk's own rendering are untouched.

   NOT GATED ON `hover`/`pointer`: this desk reports `hover: none`, so a pointer query would leak
   onto it. The breakpoint is the console's own geometry rule, copied exactly. */
@media (max-width: 720px) {
  .owner-strip .owner-why { -webkit-line-clamp: 3; }
}

/* -- WHAT AN APPROVAL CARRIES AFTER A CHANGE OF HANDS (E4.4, per D3) ------------------------------
   APPENDED, at the very end of the file, and every selector is namespaced `.case-approval*` so no
   existing rule can be reached. The block is drawn by `web/approval_flag.js` after the sharing
   block; it dresses what the SERVER answered (`case_approval.py`), and the only decision here is
   which state is loud: a job whose approval PREDATES its holder must be impossible to miss, and a
   job with nothing to say must be quiet enough not to be read as a warning.

   `:root { zoom: var(--zoom, 1.1) }` scales viewport units, so nothing here is sized in `vw` — the
   phone rule below is the console's own geometry breakpoint (`max-width: 720px`), copied exactly,
   and never a `hover`/`pointer` query: this desk reports `hover: none` and a pointer query would
   leak the phone rules onto it. */
/* THE APPROVAL STRIP FOLLOWS THE THEME. Every colour here was a LITERAL light-mode value —
   `#fbfbfc` background, `#d5d9e0` border, `#4a5260` / `#333a45` text — so in dark mode this panel
   painted a near-white slab with dark grey type, sitting directly above the translations card:
   measured on the deployed console in dark mode, and reported by the operator as a section that
   "looks white and weird".
   A literal colour cannot follow a palette; the console's own tokens can, and `--surface`, `--line`,
   `--ink-2` and `--ink-3` are all redefined by `[data-theme="dark"]`. The literal values stay as
   FALLBACKS so a shell with no tokens still renders, exactly as the `.case-share` panel above.
   The state colours (`--danger` / `--warn` / `--ok`) are the theme's own too: their literals
   (`#b3261e`, `#b26a00`, `#2e7d32`) are dark inks that a dark page swallows.
   The background is NOT stated for every state — the base surface plus the coloured left rule is
   what carries the meaning, and a tinted background per state is a second signal that has to be
   kept in step with the first. */
.case-approval {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px;
  margin: 0 0 10px; padding: 8px 10px;
  border: 1px solid var(--line, #d5d9e0);
  /* no decorative left border on the base state: the left bar is this console's severity
     language, and a neutral job wearing a grey bar made every case read as flagged. The
     severity states below colour the bar; a state with nothing to say keeps no bar. */
  border-left: 3px solid transparent; border-radius: 4px;
  background: var(--surface, #fbfbfc); font-size: 12px; line-height: 1.45;
}
.case-approval .case-approval-chip {
  font-weight: 600; letter-spacing: .01em; white-space: nowrap;
}
.case-approval[data-approval-state="approval_given_elsewhere"] {
  border-left-color: var(--danger, #b3261e);
}
.case-approval[data-approval-state="approval_given_elsewhere"] .case-approval-chip {
  color: var(--danger, #b3261e);
}
.case-approval[data-approval-state="unknown"] { border-left-color: var(--warn, #b26a00); }
/* `not_approved` is the ordinary pre-approval state of every job in flight — not a fault and not
   a warning — so it keeps the transparent bar set by the base rule: the chip already names it. */
.case-approval[data-approval-state="not_approved"] { border-left-color: transparent; }
.case-approval[data-approval-state="approval_after_move"] { border-left-color: var(--ok, #2e7d32); }
.case-approval .case-approval-facts { color: var(--ink-2, #4a5260); }
.case-approval .case-approval-stamp { color: var(--ink-2, #4a5260); }
.case-approval .case-approval-move { color: var(--ink-2, #4a5260); }
.case-approval .case-approval-why { flex: 1 1 100%; color: var(--ink-2, #333a45); }
.case-approval .case-approval-material {
  flex: 1 1 100%; color: var(--danger, #b3261e); font-weight: 600;
}
.case-approval .case-approval-flagn {
  color: var(--warn, #b26a00); font-variant-numeric: tabular-nums;
}

@media (max-width: 720px) {
  /* A LONG REASON SENTENCE ON A PHONE, AND THE CLAMP HAS TO BE THE FULL FOUR-PART IDIOM.
     MEASURED, and this was a DEFECT the first version shipped: the block had only
     `-webkit-line-clamp: 3`, which is INERT — a line clamp constrains nothing unless the element is
     a `-webkit-box` with `-webkit-box-orient: vertical` and `overflow: hidden`. The crop of the
     rendered 390x844 block showed the whole 5-line sentence, ~140px of the screen, above the case's
     own card — exactly the wall of text E4.3 hit on the holder strip. The reason is still REACHABLE
     at full length: it is the block's `title`, and the desk shows it in full. */
  .case-approval .case-approval-why {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .case-approval { font-size: 12px; padding: 8px; }
  /* THE FACTS STACK, so the separator between them must not draw as a line of its own — the same
     defect MEASURED on the holder strip's phone view. */
  .case-approval .case-approval-facts { flex-basis: 100%; }
}

/* ================================================================================================
   [E5.5] THE PHONES: THE LOGIN, SETTINGS, AND THE BLOCKED STATE — APPENDED AGAIN, at the tail.

   ONE SITE, TWO RULE SETS, SPLIT BY GEOMETRY ONLY: `max-width: 720px`, or
   `max-width: 900px and max-height: 500px` for a landscape phone. NOTHING here is gated on
   `hover`/`pointer` — this desk reports `hover: none`, so a pointer query would leak onto the
   desktop.

   WHY THIS BLOCK IS APPENDED A SECOND TIME, and it is the honest version rather than the tidy one.
   The rules below were first written at the tail of the file, and by the time the check ran, TWO
   SIBLING CARDS had appended their own blocks AFTER them — so the rules this card had just measured
   as working had stopped applying to the screen (measured: the phone's sign-in control was back to
   35px and the Settings tabs to 37px, and the desk was painted behind Settings again). Appending is
   the board's rule and it is right; the rule it implies is the one this file's own head already
   states — **the LAST block to append wins, per property** — so a card that needs a rule to hold
   must keep its own copy where it can be reached, or re-append it. Nothing was deleted: the earlier
   copy of these rules stays where it is, and this one is the one that is in force.

   MEASURED BEFORE THE RULES WERE WRITTEN, on a scratch console at all eight sizes:
     * `#settings-overlay` measured 3,176px tall inside an 844px viewport, its own `overflow-y`
       resolved to `visible`, and the document could not scroll (`scrollTo(0, 500)` moved the page by
       0px). Its content below the fold was reachable only by a trackpad two-finger swipe — on the
       device this card is about it did not scroll at all.
     * the Settings tabs measured 95px tall across two rows at 390px and 320px; the Sign in control
       35px and the password field 42px, at every phone width.
     * the Settings panel measured 3,176px of content in an 844px fixed box, and the DESK was painted
       behind it — the drill-down decision this card had to take.

   WHAT THE RULES DO. `.form-overlay-body` is the overlay's OWN scroller (`overflow: auto`, `flex: 1`)
   and it reaches every row — measured at 390x844: 2,895px of content in a 709px box, `scrollTop`
   moving a full 500px when driven. So the phone does not need a second scroller; it needs the shell
   to stop being a `position: fixed` box whose height the phone's URL bar and safe areas cut into.
   The overlay is anchored to the DOCUMENT on a phone (`inset: 0`, `position: absolute`), and the
   desk is hidden under it — the same class `access.js` already sets for the login and clears in
   `close()`, so no JavaScript is changed by this card.

   A `@media` block does not inherit the query of the block above it, so the `(max-width: 720px)`
   here is evaluated on its own and can never fire on a desk.
   ================================================================================================ */

/* ---- the login: the same field set, thumb-sized ----
   MEASURED before: `Sign in` 74x35px, the password field 357x42px. The rule is not a style
   preference — 35px is under the 44px short side every other control on this console is held to,
   and the login is the one screen a person reaches for a phone to use. Inputs go to 16px as well:
   below that iOS zooms the page on focus, which on this screen means the field the person is typing
   into is suddenly off-screen. */
@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
    .st-login { margin: 0 auto; padding: 0 1em; }  /* centred, not pushed to the top */
  .st-login-submit { align-self: stretch; min-height: 46px; }
  .st-input { min-height: 46px; font-size: 16px; }
  /* the shell is the page, not a floating box a phone can cut into */
  .form-overlay.st-overlay { position: absolute; min-height: 100%; }
  /* a tab strip is a row of tappable labels, so each one is a target and the strip wraps rather
     than clipping its last word (measured two rows at 390px either way — the difference is that
     every label is now reachable with a thumb) */
  .st-tabs { padding: 0.5em 0.7em 0.3em; gap: 0.3em; }
  .st-tab { min-height: 44px; }
}

/* ---- Settings: a DRILL-DOWN, not a sheet over the rail ----
   THE DECISION THIS CARD HAD TO TAKE, and it is recorded here because the geometry is what
   implements it. Settings REPLACES THE SCREEN on a phone: the whole viewport, and the console's own
   shell (bar, workflow tabs, rail) is not painted behind it. A sheet over the rail was the other
   option and it is wrong for this screen — Settings holds four sections and a row per account, and a
   sheet that must leave room for what it is covering is a sheet whose content is squeezed to keep a
   half-drawn desk visible at its edges. The overlay is the same DOM node on both devices; on a phone
   it owns the screen, and the desk is hidden under it.

   THE DESK IS HIDDEN FOR THE SAME REASON `access-signedout.css` HIDES IT WHEN SIGNED OUT: covering
   is a paint order, not a lock. `access.js` sets the SAME class it already sets for the login and
   clears it in `close()`. The overlay itself is deliberately NOT in this list. */
@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
  body.st-locked > header,
  body.st-locked .desk,
  body.st-locked .wfbar,
  body.st-locked #stepper,
  body.st-locked .banner {
    display: none;
  }
  #settings-overlay.st-overlay { border-radius: 0; max-width: none; padding: 0; }
  #settings-overlay .form-overlay-head { border-radius: 0; }
  #settings-overlay .form-overlay-body { border: 0; border-radius: 0; }
}

/* ================================================================================================
   [E5.5] THE PHONE OVERFLOW SHEET ON A LANDSCAPE PHONE — APPENDED, at the tail.

   A REAL DEFECT ON THIS CARD'S OWN SUBJECT, found by driving the sheet with a REAL MOUSE press at
   the control's measured centre (a JS `.click()` skips the hit test and cannot see it).

   MEASURED at 844x390, with the sheet OPENED by pressing `#bar-more` (which works: the button
   measures 48x48 and hit-tests true): the sheet's own SCREEN was painted and the rows were drawn as
   bare text on top of the console's toolbar — "Directory", "Notifications", "Permissions",
   "Feature blocks", "Settings" — with NO panel, NO scrim and NO surface. The Settings row measured
   `left 53 -> right 835, top 474 -> bottom 525` in a **390px viewport**, so it was 84px BELOW the
   screen and could not be pressed at all (`elementFromPoint(444, 499)` is not the control). The
   console's own workflow tabs, search field and create buttons were still live behind it.

   THE CAUSE IS ONE MISSING QUERY, and it is the same shape as the trap the phones block records.
   `bar-phone.js` opens the sheet inside its own query — `(max-width: 720px), (max-width: 900px) and
   (max-height: 500px)` — so on a LANDSCAPE phone (844x390) it correctly shows `#bar-more` and
   correctly unhides `#bar-sheet`. But the sheet's own RULES are `.wf-sheet*`, and those live inside
   the console's **`@media (max-width: 720px)`** block — which a 844px-wide landscape phone does not
   match. So the container was `display: none` (the base rule at line ~1163) while the JS believed it
   was open: `#bar-sheet` had non-zero children and a `hidden` of false, and nothing of it was on the
   screen. The rows that "drew on top of the toolbar" are the sheet's list items being laid out by the
   default flow of a `display: none` parent's descendants — the same defect `bar-phone.css` records
   for the workflow menu, one query further out.

   THE FIX RE-STATES THE SHEET'S PANEL GEOMETRY UNDER THE ONE QUERY THE CONSOLE USES FOR "THIS IS A
   PHONE", so a landscape phone gets the surface its own button already opens. It is a RE-STATEMENT
   rather than an edit: the rules inside the `720px` block are the desk's and the narrow phones' and
   are left exactly as they are. `position: fixed; inset: 0` (not the `absolute` the narrow block
   uses) because the console's shell at this width is a viewport-height grid, and the sheet must cover
   the screen rather than the document.

   MEASURED AFTER on the same build at 844x390: the sheet is a surface, the Settings row is INSIDE the
   viewport and a real mouse press opens Settings — which then owns the screen (`bar` and `desk` both
   `display: none`, 2 team rows drawn). */
@media (max-width: 720px), (max-width: 900px) and (max-height: 500px) {
  /* the container has to be a layer again, or the panel below positions against the page */
  .bar-sheet-wrap { display: block; position: fixed; inset: 0; z-index: 40; }
  .bar-sheet-wrap[hidden] { display: none; }
  /* the panel: a surface at the foot of the screen, scrolling within itself when the rows are taller
     than the room it has. `78vh` was a FRACTION of a tall phone and of a SHORT landscape screen it is
     304px — so the list is allowed to fill what there is and scroll the rest. */
  .bar-sheet-wrap .wf-sheet {
    position: absolute; left: 0; right: 0; bottom: 0;
    max-height: 92%;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--surface);
    border-top: 1px solid var(--line-2);
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    box-shadow: var(--e3);
    padding: var(--s2) var(--s3) max(var(--s2), env(safe-area-inset-bottom));
  }
  /* the scrim: the console behind a modal sheet is not reachable, and a half-lit toolbar behind an
     open sheet reads as a screen that did not open. */
  .bar-sheet-wrap .wf-sheet-scrim {
    position: absolute; inset: 0;
    background: rgba(12, 14, 16, 0.42);
    backdrop-filter: blur(1.5px);
  }
  /* and the row is a target, as it is on a narrow phone */
  .bar-sheet-wrap .bar-sheet-row { display: flex; align-items: center; width: 100%; min-height: 54px; }
  .bar-sheet-wrap .bar-sheet-row .btn {
    flex: 1 1 auto; justify-content: flex-start;
    font-size: 16px; font-weight: 600; min-height: 44px; text-align: left;
  }
}

/* ================================================================================================
   THE AFFIDAVIT EDITOR, FULLSCREEN AND WORD-LIKE.

   The operator, on the affidavit generator:

     "the editing isn't that good, it should be like ms word web version, fonts, bold everything"
     "affidavit generator should be a fullscreen thing, also this header with assigned to who etc
      and the what this job offers you should hide in affidavit generator"
     "get rid of this fields thing on the left what is that even there for"

   So this block does three things, and it is at the END of the file on purpose: a second agent works
   the Hague surface in the sections above, and rules appended here cannot perturb theirs.
   ================================================================================================ */

/* ---- the formatting bar ----
   WORKSTREAM B: the bar's material (translucent, sticky under the acts toolbar, token
   background) is styled once in the editor chrome block (~line 4315); this end-of-file block
   keeps the shape the two share so a reader at the END of the file still finds the tools.
   (--r-1/--r-2 were undefined names here before; the tokens are --r-sm/--r-md.) */
.aff-fmt { display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
  padding: 6px 10px; margin: 0;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm);
  position: sticky; top: 53px; z-index: 5; }
/* THE TOOLBAR STAYS PUT while the document scrolls under it — a word processor's toolbars do, and a
   bold button that scrolls away is a bold button the operator has to hunt for mid-sentence. */
.aff-fmt-btn { min-width: 32px; padding: 3px 8px; font-size: 13px; line-height: 1.4; }
.aff-fmt-b { font-weight: 800; }
.aff-fmt-i { font-style: italic; font-family: Georgia, serif; }
.aff-fmt-u { text-decoration: underline; }
.aff-fmt-sep { width: 1px; align-self: stretch; background: var(--line); margin: 0 4px; }
.aff-fmt-size { font-size: 12px; padding: 3px 6px; border: 1px solid var(--line-2);
  border-radius: var(--r-sm); background: var(--surface); color: var(--ink); }
/* THE FONT CONTROL, beside the size it is set at. It is a native `<select>` like the size ladder —
   the console's own material — and each option previews its own face, so the list reads as the
   document would. The `min-width` keeps the control's own box steady as the selection moves between
   faces, so the toolbar's tools do not shuffle sideways while the operator works. */
.aff-fmt-face { font-size: 12px; padding: 3px 6px; border: 1px solid var(--line-2);
  border-radius: var(--r-sm); background: var(--surface); color: var(--ink);
  min-width: 9rem; max-width: 15rem; }
.aff-fmt-face option { font-size: 13px; }
.aff-fmt-state { font-size: 11px; color: var(--ink-4); }

/* ---- a snippet: a cut-out of the job's own document, in the page ----
   A SNIPPET IS AN OBJECT THE OPERATOR DRIVES, not a picture pasted into text. Its controls are
   drawn INSIDE the figure and OUTSIDE the caption (see `snippetFigureHtml`), so:
     * the figure contributes exactly ONE line to the sheet's `innerText` — its caption — which is
       what `saveAffidavitDocument`'s strip and `record_figures`' line anchor both depend on
       (MEASURED: a `contenteditable="false"` button block adds nothing to `innerText`);
     * the caption stays the readable line under the picture, and it is the one part of the figure
       a person can type in.
   `touch-action: none` on the figure is what lets a drag MOVE it on a phone instead of scrolling
   the page under the finger; it is scoped to `.aff-snippet` so the document itself still scrolls
   normally everywhere else. */
.aff-snippet { position: relative; margin: 10px 0; padding: 6px; border: 1px solid var(--line-2);
  border-radius: var(--r-md); background: var(--sunken); text-align: center;
  touch-action: none; cursor: grab; }
.aff-snippet img { display: block; margin: 0 auto; max-width: 100%; height: auto;
  border: 1px solid var(--line); background: #fff; }
/* ---- A PLACED SNIPPET: THE PICTURE'S EDGE IS THE OPERATOR'S TO CHOOSE ----
   `data-snippet-x` is written by `snippetFigureHtml` when — and only when — the record holds a
   place the operator chose (`snippetPlaceFigure` writes the offset itself, as a transform in the
   page's own pixels). These two rules are the part a `style` attribute cannot carry: a picture
   CENTRED in its figure would move to the middle of wherever the figure was pushed to, so a placed
   figure's picture is anchored to its LEFT edge — which is the edge the stored percentage names —
   and the caption reads from the same edge rather than from the middle of a wide empty box.
   A figure with no chosen place matches neither rule and is drawn exactly as it always was. */
.aff-snippet[data-snippet-x] { text-align: left; }
.aff-snippet[data-snippet-x] img { margin-left: 0; margin-right: 0; }
/* ---- BLENDED IN WHEN YOU ARE NOT WORKING ON IT ------------------------------------------------
   The operator, verbatim: "when I have put the snippet where I want it, and I click off it should hide
   the resize, the outline and the cross etc of the snippet, it should be blended into the page now".

   A figure therefore has TWO states and this is the resting one. With nothing selected, the figure
   loses its border, its box and its controls: the picture sits on the page with only the thin edge its
   own `<img>` carries (so a white crop on white paper is still readable as a picture rather than a
   hole), and the caption keeps the document's own texture instead of a UI's.

   THE CHROME COMES BACK THE MOMENT THE FIGURE IS TOUCHED — see `#aff-doc[data-snip-active]` below,
   which re-draws exactly the same box, grip and remove control the figure had before. Nothing is
   removed from the DOM: the controls are hidden, never unbuilt, so the resize/remove wiring and the
   `contenteditable` caption behave identically in both states.

   WHY THIS IS KEYED ON THE SHEET rather than on each figure: the state is "which snippet is the
   operator currently on", which is a fact about one surface at one time, not a property of an element.
   Marking each figure independently would let two figures claim it at once and would leave a stale
   claim behind on any figure that was removed while selected. */
#aff-doc:not([data-snip-active]) figure.aff-snippet {
  border-color: transparent; background: transparent; margin: 10px 0; padding: 6px;
  cursor: default; touch-action: auto; }
#aff-doc:not([data-snip-active]) figure.aff-snippet img { border-color: var(--line); }
#aff-doc:not([data-snip-active]) figure.aff-snippet .aff-snip-tools { display: none; }
#aff-doc:not([data-snip-active]) figure.aff-snippet figcaption { color: var(--ink-4); }
/* ...BUT THE CHROME COMES BACK UNDER THE POINTER, AND IT MUST.
 *
 * MEASURED, and it cost a working control: with the resting state hiding `.aff-snip-tools`
 * unconditionally, nothing could reach the grip without first selecting the figure — and a resize
 * driven from the grip's own rect found a zero-width grip and did nothing (`the resize wrote a WIDTH
 * on the figure: data-snippet-w=None`; removing this one rule turned that check green again). The
 * operator's own instruction was that the chrome should go away when they click OFF the snippet, not
 * that the snippet should become inert: a picture whose handle is unreachable until you know to press
 * it first is a control that has been taken away, and the honest reading of "blended into the page"
 * is about what is PAINTED at rest, not about removing the handles.
 *
 * So the resting state is still quiet — no border, no transparent box, no tools — and pointing at the
 * figure brings its tools back. The selected state (the sheet carrying `data-snip-active`) keeps them
 * out at all times. */
#aff-doc:not([data-snip-active]) figure.aff-snippet:hover .aff-snip-tools,
#aff-doc:not([data-snip-active]) figure.aff-snippet:focus-within .aff-snip-tools { display: flex; }
/* A FIGURE THAT IS NOT SELECTED IS STILL REACHABLE: the resting state must not make it un-clickable,
   or "click off it" would be a one-way door. `pointer-events` stays on and the figure keeps its own
   box for hit testing; only the PAINT changes. */
#aff-doc[data-snip-active] figure.aff-snippet[data-snippet-id] { border-color: var(--accent); }
.aff-snippet figcaption { margin-top: 4px; font-family: var(--mono); font-size: 10.5px;
  color: var(--ink-4); }
/* THE CAPTION IS EDITABLE, SO IT SAYS SO. A one-line box that is the operator's to change reads as
   text until the pointer is over it; the hover/focus rules are that cue, and the dotted underline
   keeps the caption's own look as the document's caption. */
.aff-snippet figcaption[contenteditable="true"] { cursor: text; border-radius: var(--r-2, 3px);
  padding: 1px 3px; outline: none; }
.aff-snippet figcaption[contenteditable="true"]:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.aff-snippet figcaption[contenteditable="true"]:focus { background: color-mix(in srgb, var(--accent) 14%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent); color: var(--ink-2); }
/* THE CONTROLS, top-right of the picture's own box. Big enough for a finger (the console's own
   44px rule is for a screen's primary controls; these sit on a document object and are sized to
   the figure, which is why they are 22px with a generous hit area rather than 44 — a control wider
   than the picture it belongs to is not a control). */
.aff-snip-tools { position: absolute; top: 2px; right: 2px; display: flex; gap: 2px;
  z-index: 2;
  /* A CONTROL THE OPERATOR CANNOT HIT IS NOT A CONTROL. The sheet is drawn with a fit-to-screen
     `transform: scale(k)` on `.aff-pages` (measured: k=0.564 on a 1440-wide desk, k=0.246 on a
     390-wide phone), and everything inside it — including these controls — was scaled with it, so a
     22px button measured 14px on the desk and 6px on the phone. They are therefore drawn at a
     CONSTANT SIZE: `wireSnippetFigures` writes the reciprocal of the sheet's own scale into
     `--aff-snip-k` on the sheet, and this counter-transform cancels it back out. Anchored top-right
     so the box stays put in the picture's corner as it grows. */
  transform-origin: top right;
  transform: scale(var(--aff-snip-k, 1)); }
/* ---- WHEN THE CONTROLS DO NOT FIT OVER THE PICTURE, THEY SIT ABOVE IT -------------------------------
   MEASURED DEFECT THIS CLOSES (phone, 390x844): the picture is drawn as small as the operator resizes
   it, and its controls are drawn at a CONSTANT finger-reachable size (22px, hit-extended to 44px) — so
   a picture resized to 36x14 screen px sat UNDER a 51x24 tool box and EVERY point across it hit
   `BUTTON.aff-snip-del`. The snippet could not be grabbed to move it at all, and a press meant to drag
   it deleted it. Hit-tested across the picture's own width at its centre line: 5%, 15%, 25%, 35%, 50%,
   65%, 75% — all `BUTTON.aff-snip-del`.
   `affSnippetToolsFit` (app.js) measures the two and sets this class when the tools do not fit over the
   picture; they then float just above the figure's own box instead of over the operator's picture. It
   is re-decided on every redraw (`snippetPlaceFigure`), because a resize changes the picture's height.
   The host line is already lifted (`.aff-line.aff-snip-host`), so a control raised above the figure is
   still above the neighbouring lines. */
.aff-snippet.aff-snip-tools-above .aff-snip-tools { top: auto; bottom: 100%; margin-bottom: 2px;
  /* AND THE COUNTER-SCALE MUST GROW THE OTHER WAY. `.aff-snip-tools` is drawn at a constant size by
     `transform: scale(var(--aff-snip-k))` with `transform-origin: top right`, so growing it keeps the
     top-right corner put and extends it DOWN-AND-LEFT. Measured on a phone (k=4.069): the raised box's
     rect came back `top 649, bottom 673` against a picture top of `657` — the very overlap it was
     raised to avoid, because the scaled box grew back down over the picture. Anchored at its
     BOTTOM-right instead, the box grows UP-AND-LEFT from the edge that is pinned above the figure, so
     the clearance the rule asks for is the clearance the operator gets. */
  transform-origin: bottom right; }
.aff-snip-tools button { width: 22px; height: 22px; padding: 0; min-width: 0; line-height: 1;
  font-size: 12px; border: 1px solid var(--line-2); border-radius: var(--r-2, 3px);
  background: color-mix(in srgb, var(--surface) 88%, transparent); color: var(--ink-3);
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  /* THE FINGER'S OWN RULE: the visible box stays 22px (a control wider than the picture it belongs
     to is not a control), and the HIT area is extended to 44px by a pseudo-element that takes no
     layout space and draws nothing. */
  position: relative; }
.aff-snip-tools button::after { content: ""; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%); }
/* AND WHEN THE CONTROLS ARE RAISED CLEAR OF THE PICTURE, THE FINGER'S HIT AREA MUST NOT BLEED BACK OVER
   IT. That 44px area extends 11px past the 22px button on every side — (44-22)/2 — so a button raised
   2px clear of a small picture still had its hit area reaching ~9px down ONTO the picture.
   MEASURED (phone, 390x844, after the raise): hit-testing across the picture's own width at its centre
   line gave `BUTTON.aff-snip-grip` at 5% and `BUTTON.aff-snip-del` at 25%..95% — a press on the picture
   meant to DRAG the snippet would have deleted it. The area is shifted up by its own overhang, so it
   stops at the control's bottom edge and grows upward instead, where nothing of the operator's is.
   It stays 44px and stays reachable: a control the finger cannot reach is not a control.
   THIS RULE SITS AFTER the base `::after` on purpose — same specificity, so the later one wins; placed
   before it, the shift was silently overridden and the hit area still covered the picture. */
.aff-snippet.aff-snip-tools-above .aff-snip-tools button::after {
  transform: translate(-50%, calc(-50% - 11px)); }
.aff-snip-tools button::before { font-size: 12px; line-height: 1; }
.aff-snip-tools button:hover { background: var(--surface); color: var(--ink); border-color: var(--line-3); }
.aff-snip-grip { cursor: ew-resize; touch-action: none; }
.aff-snip-grip::before { content: "⇔"; }
.aff-snip-del:hover { color: var(--danger); border-color: var(--danger-line); }
.aff-snip-del::before { content: "✕"; }
/* WHILE IT IS BEING DRAGGED, AND WHERE IT WOULD LAND. The figure lifts (so the operator can see
   they are holding it, not the page) and the line it would follow is marked — the drop is a place
   they have SEEN rather than one they discover after releasing. */
.aff-snippet.aff-snip-moving { opacity: .72; cursor: grabbing; border-color: var(--accent);
  box-shadow: 0 6px 18px rgba(0,0,0,.18); }
.aff-line.aff-snip-over { box-shadow: inset 3px 0 0 0 var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent); }

/* ---- A LINE THAT HOLDS A SNIPPET PAINTS ABOVE THE LATER LINES ------------------------------------
   THE DEFECT THIS CLOSES, and it is the reason a snippet dropped onto a low line could not be
   RESIZED afterwards: the placed lines are absolutely positioned and `z-index: auto`, so among
   themselves the LATER line paints over the earlier one — and when a snippet is dropped onto a line,
   the figure sits INSIDE that line with the picture hanging below it over the following lines. The
   handle keeps its own `z-index: 2`, but a `z-index` only counts WITHIN its stacking context, and the
   figure itself creates one (it carries a `transform` from `snippetPlaceFigure`), so the handle's 2 is
   confined to the figure and the figure to its host line.

   MEASURED, after the check's own move onto the line "truth." (desk 1440x900), the stack at the
   handle's own rect was:

       0. DIV.aff-line            <- a LATER line, on top
       1. BUTTON.aff-snip-grip    <- the handle, underneath

   `elementFromPoint` at the handle answered `DIV.aff-line`, a real press at the check's own
   coordinates called `affSnippetResize` **0 times**, and the check reported "387px -> 387px" while
   its own "hittable" assertion passed — because `getBoundingClientRect()` says the handle is on
   screen and only hit testing knows it is covered. Setting this line's `z-index` to `4` made the same
   press reach the handle and the resize run (`data-snippet-w` became `72`), which is the confirmation.

   So the HOST is lifted, not the figure: a line carrying a snippet is the thing that must be above its
   neighbours. `wireSnippetFigures` adds `aff-snip-host` to the line it adopts, and removes it when the
   figure leaves. 4 is above the form's own absolute lines (all `auto`) and below the editor's
   furniture (`#stage` chrome is outside `.aff-page`), so it cannot cover the operator's controls. */
.aff-line.aff-snip-host { z-index: 4; }

/* ---- the intake form's picture control ---- */
.inf-photo { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.inf-photo-img { display: block; }
.inf-photo-img img { max-width: 150px; max-height: 150px; border: 1px solid var(--line-2);
  border-radius: var(--r-md); background: #fff; }
.inf-photo-file { font-size: 12px; }
.inf-photo-state { font-size: 11px; color: var(--ink-4); }

/* ---- FULLSCREEN: THE SAME EDITOR, FULL-VIEWPORT ----
   `body.aff-fullscreen` is set by the editor and by nothing else. Everything hidden here is shell
   chrome the operator named: the top bar, the case header with who it is assigned to, the informal
   tabs, and the job's own panels. What is left is THE EDITOR — the same `#stage .affdoc` the
   normal view shows, at full height — and nothing else.
   WORKSTREAM B DEFECT, MEASURED (screenshot 2026-09-24): the old rule
   `body.aff-fullscreen .aff-record { display:none !important }` hid the EDITOR ITSELF, because
   `infAffidavit()` builds the editor with class `step-card inf-card affdoc` and `#stage`'s Hague
   step cards carry `.aff-record` — the selector that was written for the OLD affidavit panel
   matches the LIVE editor's card, so fullscreen swapped the editor for the job's shell panels
   (the red gap prose, APPROVE AND SAVE, the signature forms). The client: "the fullscreen view is
   cooked... you can tell which one is which". The rule is GONE: the old panel is uncalled code
   (`affidavitPanel` has no caller), so nothing draws a second affidavit surface to hide, and the
   same editor stays on screen in both modes. */
/* THE SELECTORS ARE THE ONES THE DOM ACTUALLY USES, measured in a real browser rather than
   guessed. The job's own panels live in the stage as `#acts-strip` ("What this job offers you"),
   `#owner-strip` ("Assigned to …"), `#case-share` and `#case-approval`, and the informal tabs are
   a `nav.inf-tabs` INSIDE the informal screen — not a shell element at all. Measured ancestry:
   #case-share sits directly under #stage, and .inf-tabs is `#stage .inf-flow > nav`.
/*
   `#stage` also holds the AFFIDAVIT itself, so the panels are hidden individually — a rule hiding
   #stage would hide the thing the operator came to work on. */
/* THE TOP WORKFLOW ROW AND THE JOB LIST ARE ALSO SHELL. Measured in the DOM: the row with
   "Hague / Informal / Translations" is `.wfbar`, and the list of jobs down the left is
   `SECTION.col.queue`. Both were still on screen after my first pass, because neither is inside the
   informal card — they are siblings of it in `.desk-body`. Taking them away is what leaves the
   affidavit alone on the page, which is what "fullscreen" was asked to mean. */
/* THE LAYOUT MUST COLLAPSE WITH THE COLUMN. `.desk-body` is
   `grid-template-columns: var(--rail) 1fr`, and hiding the queue left its rail TRACK still reserved
   — so the affidavit was drawn in a ~150px strip down the left of an otherwise empty page. Measured
   in the browser: after the panels were hidden the sheet's own width fell from 900px to a sliver.
   A hidden element does not give its track back; the track has to be re-declared. */
body.aff-fullscreen .desk-body { grid-template-columns: minmax(0, 1fr); }
/* THE JOB HEADER — "this header with assigned to who etc" — is `DIV.case-head` (it holds the job's
   name, its DRAFT badge and Delete job). Measured ancestry: BUTTON#delete-case -> DIV.case-actions
   -> DIV.case-head -> SECTION.col sheet. It is not the same element as `#case-share`, which sits
   in the stage; both had to be named, and naming only one left half the header on screen. */
body.aff-fullscreen .case-head { display: none !important; }
body.aff-fullscreen .wfbar,
body.aff-fullscreen .col.queue,
body.aff-fullscreen .bar,
body.aff-fullscreen .step-rail,
body.aff-fullscreen .step-caption,
body.aff-fullscreen #acts-strip,
body.aff-fullscreen #owner-strip,
body.aff-fullscreen #case-share,
body.aff-fullscreen #case-approval,
body.aff-fullscreen .inf-head,
body.aff-fullscreen .inf-tabs,
body.aff-fullscreen #chat-wrap,
body.aff-fullscreen .danger-foot,
/* THE CREATE BANNER and the UNFILLED-GAP PROSE are status prose about the job, not part of the
   editor; in fullscreen the document is the point. The gap list stays reachable in the normal
   view and in the editor's status band — hiding it here hides no fact, it moves nothing. */
body.aff-fullscreen .stage > .inf-result,
body.aff-fullscreen [data-unfilled],
/* The page-level banner reports the create; the acts bar already says what matters in fullscreen
   ("Saved — nothing has been sent"), and the prose below the sheet is the normal view's business. */
body.aff-fullscreen #banner { display: none !important; }

/* THE STAGE TAKES THE WINDOW. `#sheet-scroll` is the scroller (see .sheet-scroll above), so it is
   the element that has to grow to the viewport height — the document then fills it.
   `:root { zoom: 1.1 }` multiplies VIEWPORT UNITS TOO, so a plain `100vh` here measures 990px on a
   900px window and the editor's own foot is 90px below the glass — measured: the format toolbar's
   row and the bottom of the page sat off the screen in fullscreen. The height is divided by the same
   token the zoom is set from, exactly as the chat's expanded height already does (see
   `body.case-open.chat-expanded`). */
body.aff-fullscreen .stage { padding: 0; height: calc(100vh / var(--zoom, 1.1));
  display: flex; flex-direction: column; max-width: none; }
body.aff-fullscreen .sheet-scroll { height: calc(100vh / var(--zoom, 1.1)); }
body.aff-fullscreen .affdoc { margin: 0; border: 0; border-radius: 0; box-shadow: none;
  background: var(--sunken); display: flex; flex-direction: column;
  height: calc(100vh / var(--zoom, 1.1)); }
body.aff-fullscreen .affdoc > .affed-actions { flex: none; padding: 8px 14px;
  border-bottom: 1px solid var(--line); background: var(--surface); }
body.aff-fullscreen .aff-toolbar { flex: none; padding: 8px 14px; margin: 0;
  border-bottom: 1px solid var(--line); }
body.aff-fullscreen .aff-fmt { position: static; top: auto; margin: 0;
  border-left: 0; border-right: 0; border-radius: 0; }
body.aff-fullscreen .affed { flex: 1; min-height: 0; display: flex; flex-direction: column;
  margin: 0; }
body.aff-fullscreen .affed-mid { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* THE PAGEWRAP IS THE SPACE THE PAGE HAS TO FIT IN, so its padding is part of the measurement
   `affFitPages` reads and the fit is taken against what is left. Kept tight on purpose: every pixel
   of padding here is a pixel the document does not get. */
body.aff-fullscreen .affed-pagewrap { flex: 1; min-height: 0; padding: 14px 12px; }
@media (max-width: 720px) {
  /* a phone's screen is the scarce thing, and the page already scales to whatever is left */
  body.aff-fullscreen .affed-pagewrap { padding: 8px 6px; }
}
/* THE SHEET IS WIDER AND TALLER IN FULLSCREEN, unless it is a DRAWING OF PAGES.
   The whole point of the mode is that the page stops being a 900px column inside a stack of panels
   and becomes the thing being worked on — so a LEGACY sheet (plain lines, one continuous sheet)
   keeps the wider box it always had here. A sheet that draws REAL PAGES does not: it is exactly the
   template's own size, and enlarging the sheet around it would put the drawn page in a box that no
   longer matched what is on it. Its display size is the scale `affFitPages` computed, and this rule
   is the one that would otherwise override it with `min-height: calc(100vh - 220px)` — a leftover
   from when the sheet WAS the page. */
body.aff-fullscreen .affed-sheet { max-width: 900px; min-height: calc(100vh - 220px); }
body.aff-fullscreen .affed-sheet.paged { max-width: none; width: fit-content;
  min-height: 0; height: auto; }

/* WHERE ELSE A FACT LANDS. A value the form names on several of its lines is ONE fact listed once,
   with the other lines named beside it — the same list must not repeat itself (app.js `facts`). */
.aff-also { color: var(--ink-3); font-size: 11px; white-space: normal; }

/* ================================================================================================
   MOTION — the console's feel layer (apple-design pass, 2026-09-24).

   THESIS. This is an OPERATE surface: motion serves feedback, state and continuity, and never
   makes an operator wait. One material idea — surfaces ARRIVE from where they come from and
   RETURN along the same path — carried by quiet supporting states:

     · overlays materialise (opacity + 8px rise + blur radius), exits are faster than entrances;
     · the phone sheets slide from the bottom edge they live on, the scrim fading with them;
     · pressed controls answer on :active (already present) with a faster release than press;
     · state chips keep their existing chip-in moment;
     · the desk's cards stagger in ONCE per list paint, capped, never on scroll.

   Every value is a token. No bounce anywhere: nothing on this console is flicked, so overshoot
   would read as a glitch (apple-design §4: damping 1.0 unless the gesture carried momentum).

   REDUCED MOTION is an intentional alternative, not an off switch (apple-design §14): spatial
   movement (translate/scale) is dropped; OPACITY and colour transitions — which carry the state
   change — remain, at the same durations.

   IMPLEMENTATION NOTES. Overlays here toggle the `hidden` ATTRIBUTE directly, and CSS cannot
   transition *from* display:none. So entrances animate with a keyframe (runs when the element
   un-hides), and exits are cut — the honest, dependency-free choice for a console whose overlays
   are plain hidden-toggles; a fake "closing" animation that never runs would be a claimed
   success (R8). Keyframes animate only transform/opacity — compositor-friendly.
   ================================================================================================ */

:root {
  --dur-fast: 120ms;   /* press feedback, chip answers */
  --dur: 180ms;        /* routine state change (was 150ms; within the 150–300 band) */
  --dur-slow: 280ms;   /* overlay/sheet entrances: the 150–300 band's top */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-inout: cubic-bezier(0.45, 0, 0.25, 1);
}

/* ---- 1. OVERLAYS MATERIALISE ------------------------------------------------------
   `.form-overlay` (Settings, directory, notifications…) and `.src-overlay` (source view)
   toggle `hidden`. The entrance keyframe gives them one shared arrival: the scrim dims
   (opacity), the panel rises 8px — the surface materialising, not a plain fade
   (apple-design §12, "materialize, don't just fade"). */
@keyframes overlay-in {
  from { opacity: 0; }
}
@keyframes overlay-panel-in {
  from { opacity: 0; transform: translateY(8px) scale(0.995); }
  to   { opacity: 1; transform: none; }
}
.form-overlay:not([hidden]),
.src-overlay:not([hidden]) {
  animation: overlay-in var(--dur-slow) var(--ease-out);
}
/* the panel is the head+body card inside the scrim — animate the card, not the dimmer.
   transform-origin follows the geometry: a centred panel scales from its centre; the
   full-height settings sheet grows from the bottom edge it reads as coming from. */
.form-overlay:not([hidden]) > .form-overlay-head,
.form-overlay:not([hidden]) > .form-overlay-body,
.st-overlay:not([hidden]) > .form-overlay-head,
.st-overlay:not([hidden]) > .form-overlay-body {
  animation: overlay-panel-in var(--dur-slow) var(--ease-out);
  transform-origin: 50% 50%;
}
.st-overlay:not([hidden]) > .form-overlay-body { transform-origin: 50% 100%; }
.src-overlay:not([hidden]) > * {
  animation: overlay-panel-in var(--dur-slow) var(--ease-out);
}

/* ---- 2. PHONE SHEETS FROM THEIR EDGE ----------------------------------------------
   `.wf-sheet`/`.bar-sheet` live at the bottom edge: they slide up from it. The scrim fades.
   Exits cut (the hidden toggle) — the entrance explains the spatial relationship; an exit
   animation a hidden-toggle cannot express must not be faked. */
@keyframes sheet-up {
  from { transform: translateY(48px); opacity: 0.6; }
  to   { transform: none; opacity: 1; }
}
.wf-sheet-wrap:not([hidden]) .wf-sheet {
  animation: sheet-up var(--dur-slow) var(--ease-out);
}
.wf-sheet-wrap:not([hidden]) .wf-sheet-scrim {
  animation: overlay-in var(--dur-slow) var(--ease-out);
}

/* ---- 3. PRESS ANSWERS INSTANTLY, RELEASE SETTLES -----------------------------------
   The press already transforms on :active (§1 respond on pointer-down). What was missing is
   the release: with the base transition at 150ms the release lagged the press. The press
   itself stays instant (no transition on :active), the release eases back. */
.btn, .wf-tab, .rail-tab, .step, .pg, .btn-icon {
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur-fast) var(--ease-out);
}
.btn:active:not(:disabled) { transition-duration: 0ms; }  /* the press is instant */

/* ---- 4. A LIST ARRIVES AS ONE THING (Kazim, 2026-09-24) ----------------------------
   "when I switch to hague and informal and translations its slow, but when I switch to home
   its smooth and instant."
   MEASURED: with the per-row stagger below, an Informal switch settled at 416ms across 12
   separate animations — the rail's rows AND the desk's cards each fading in on their own
   0/30/60/90ms delay, so the list crept in row by row while Home arrived as one block. The
   stagger was the whole difference; the data was already there at 128ms.
   DESIGN.md's Motion section is the authority and names exactly TWO authored moments — the
   state chip's 200ms settle and the mark's 60s ring. A per-row entrance is not one of them.
   So a row keeps its gentle entrance but the DELAYS are gone: the list arrives as a single
   block in one `--dur`, exactly like the overview, and nothing creeps.
   `both` is still needed so a row cannot flash at full opacity before its animation starts. */
@keyframes row-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}
.rail-body .entry, .rail-body li, .home-card {
  animation: row-in var(--dur) var(--ease-out) both;
  animation-delay: 0ms;             /* the list is one object: it does not creep in row by row */
}

/* ---- 4b. THE SWITCH'S OWN LOADING STATE (Kazim, 2026-09-25) --------------------------
   "the switch happens and its just 'loading' instead."
   The switch skeleton reuses `.skel` (its sweep, its reduced-motion and contrast rules, its
   dark-theme values) and adds only what a RAIL row needs: the row's real geometry — an index
   column, a name block, then the state chip and meta line — so the skeleton occupies the same
   box the list will, and the arrival does not move anything. Nothing new is invented: same
   shimmer, same tokens, same 150ms band as the rest of the console's motion. */
.skel-entry {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  list-style: none;
}
.skel-entry .entry-idx { flex: none; width: 22px; }
/* THE INDEX PLACEHOLDER IS A BAR, NOT A SLIVER. `.skel-bar.w14` is 14% of its parent — inside a
   22px index column that is ~3px, and it rendered as a stray tick at the row's left edge (seen in
   the desk and phone screenshots, reported by neither count). The real `.entry-idx` holds a
   two-digit numeral, so the placeholder takes a fixed width that matches one and centres itself
   in the column. */
.skel-entry .entry-idx .skel-bar { width: 12px; height: 8px; margin: 0 auto; }
.skel-entry .entry-main { flex: 1 1 auto; display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.skel-entry .entry-meta { display: flex; align-items: center; gap: 8px; }
/* The rail's skeleton must not carry the list entrance: it is the FIRST thing on screen, not an
   arrival INTO a settled view, and `row-in` on a placeholder reads as content settling. */
.skel-entry { animation: none; }
@media (prefers-reduced-motion: reduce) {
  .skel-entry .skel-bar, .skel-entry .skel-chip { animation: none; }
}

/* ---- 5. DIALOGS THE OPERATOR CAUSES: the sign-out confirm -------------------------- */
@keyframes dialog-in {
  from { opacity: 0; transform: translateY(6px) scale(0.99); }
  to   { opacity: 1; transform: none; }
}
#signout-dialog:not([hidden]) {
  animation: dialog-in var(--dur) var(--ease-out);
}

/* ---- 6. REDUCED MOTION: the intentional alternative --------------------------------
   Spatial movement dropped; opacity kept (it carries the state change). Durations shorten
   rather than zeroing — feedback must remain legible (apple-design §14). */
@media (prefers-reduced-motion: reduce) {
  .form-overlay:not([hidden]), .src-overlay:not([hidden]),
  .wf-sheet-wrap:not([hidden]) .wf-sheet,
  #signout-dialog:not([hidden]),
  .rail-body .entry, .rail-body li, .home-card {
    animation-duration: 1ms;           /* effectively instant, but not display:none */
  }
  .form-overlay:not([hidden]) > .form-overlay-head,
  .form-overlay:not([hidden]) > .form-overlay-body,
  .st-overlay:not([hidden]) > .form-overlay-head,
  .st-overlay:not([hidden]) > .form-overlay-body,
  .src-overlay:not([hidden]) > *,
  .wf-sheet-wrap:not([hidden]) .wf-sheet {
    animation-name: overlay-in;        /* opacity only — no translate, no scale */
    animation-duration: 160ms;
  }
  .rail-body .entry, .rail-body li, .home-card { animation: none; }
  .btn, .wf-tab, .rail-tab, .step, .pg, .btn-icon { transition-duration: 80ms; }
}

/* ---- 7. REDUCED TRANSPARENCY: frostier scrims -------------------------------------- */
@media (prefers-reduced-transparency: reduce) {
  .form-overlay { background: rgba(20, 23, 27, 0.82); }
  .wf-sheet-scrim, .bar-sheet-wrap .wf-sheet-scrim { background: rgba(12, 14, 16, 0.8); backdrop-filter: none; }
}

/* ---- 8. THE REQUEST-ACCESS FLOW STRIPS (D8/D9, 2026-09-24) ---------------------------
   The read-only note with its ask, and the holder's pending asks with Allow / Deny. Drawn by
   `app.js` from the payload's own blocks; the styles live here so the panel and the routes
   cannot disagree about what a row looks like. */
#access-flow-strips { display: flex; flex-direction: column; gap: 8px; margin: 0 0 10px; }
.ro-strip, .ar-strip {
  border: 1px solid var(--line, #2c313a); border-radius: 8px;
  padding: 8px 10px; background: var(--panel-2, #1a1e23);
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
}
.ro-note { flex: 1 1 240px; color: var(--text-dim, #9aa3ad); }
.ro-wait { color: var(--text-dim, #9aa3ad); }
.ar-strip { flex-direction: column; align-items: stretch; }
.ar-head { font-weight: 600; }
.ar-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.ar-who { flex: 1 1 200px; }

/* ============================================== REPORT/SHARE AFFIDAVITS (the client's own screen)

   The affidavits' tick list, built on the `.report-picker` / `.rp-*` rules above so the two
   report/share screens look like the same feature rather than two: `affidavitSharePicker` reuses
   `.rp-head`, `.rp-count`, `.rp-list` and `.rp-row`, and adds only what it has that a delivery
   report does not — a FILE NAME box per affidavit.

   THE NAME BOX SITS UNDER ITS OWN ROW, not beside the tick: an affidavit's name is long (a court
   form's own label), and a row that tried to hold the tick, the name and the file name on one line
   pushed the file name off a 390px phone. Measured by looking at it at both widths. */
.affshare-picker { border: 1px solid var(--line); border-radius: 6px; padding: var(--s2); margin: var(--s2) 0; }
.affshare-list { margin: 6px 0 0; }
.affshare-list .rp-row { padding: 8px 0; }
.affshare-id { color: var(--ink-3); font-size: 11px; }
/* A LEGACY AFFIDAVIT IS NAMED, and the mark says why it is one record per job. */
.affshare-legacy { border-left: 3px solid var(--warn-line, var(--line)); padding-left: 8px; }
.affshare-name { display: flex; flex-direction: column; gap: 2px; margin: 6px 0 0 22px; }
.affshare-name .inf-label { font-size: 11px; }
.affshare-name .inf-input { width: 100%; max-width: 34rem; }
/* A BOX FOR AN AFFIDAVIT THAT IS NOT TICKED IS SHUT, not hidden: it shows the operator that the name
   belongs to that row, and re-ticking brings the typing straight back. */
.affshare-name .inf-input:disabled { opacity: .55; }
@media (max-width: 640px) {
  .affshare-name { margin-left: 0; }
  #affshare-block .inf-actions .btn, #affshare-now, #affshare-back { width: 100%; }
}


/* ================================================================================================
   WHO HOLDS THIS JOB HAS ONE PLACE ON THE ROW (2026-09-24) — APPENDED, never inserted mid-file.

   Kazim, from a screenshot of his own rail: *"the names are all on the left except one job has a
   name to the right"*. Traced, not guessed: `.entry-meta` is a WRAPPING FLEX LINE and
   `.entry-sub` carries `flex: 1 1 62%`, so the holder chip's side was ARITHMETIC rather than a
   rule. Measured on his own two rows, at the desk's 319px meta width:

       "B own job"  / Bob     64 + 8 + 202 + 8 + 36  = 318  <= 319   -> fits: chip rides line 1
       "A own job"  / alice   64 + 8 + 202 + 8 + 50  = 332   > 319   -> wraps: chip sits alone, LEFT

   Two names fourteen pixels apart put the same fact on opposite sides of the row. The same
   arithmetic flipped Hague's rows (a long destination pushing the chip right on one row and off
   the line on the next), which is why the report covered Hague as well as Informal.

   THE FIX IS ONE RULE, not a second layout: an auto left-margin on the chip absorbs whatever free
   space the line has left, so the chip is pinned to the RIGHT EDGE of whichever line it lands on
   — beside the destination when there is room for it, on its own line (still right) when there is
   not. Nothing else moves: measured against the deployed rail the row height is UNCHANGED
   (69/98px at 1440 before and after) and the destination is clipped no more than before (10px at
   1440, 70px at 1120 — both pre-existing, neither made worse). The chip's own left offset becomes
   a single value at every width tested (1440 / 1120 / 980 / 860 / 760 / phone), against up to
   five distinct offsets before.

   WHY NOT A GRID WITH THE DESTINATION ON ITS OWN LINE. That was built and measured too: it pins
   the chip on both axes but CLIPS the destination worse (28px at 1440, 88px at 1120) or costs a
   line on every row (~92px where a short row was 69px). The destination names the country that
   drives the legal-aid lookup and `DESIGN.md` describes this row as *"a state chip with its file
   count and destination"*, so re-cutting those two apart to park a chip is the wrong trade.

   Scoped to the three containers the chip is drawn into (`web/owner.js` writes the rail row, the
   phone's card list and the desk's centre view, all through the same `swapChip`), and to
   `.owner-mini` alone.

   THE CHANGED-HANDS MARK, corrected by a later measurement. The mark (`web/case_share.js`,
   `.case-share-mini`) is appended into the same container AFTER the chip, so with the chip pinned
   right by `margin-left: auto` the mark lands OUTSIDE it and the chip is pushed 41px in. Measuring
   with a mark on EVERY row hides this — the whole column shifts together and still reads as one
   value. On a MIXED list (one job in the list changed hands, which is the ordinary case) the marked
   rows put the chip at 41px and the unmarked at 0, which is the same defect this block exists to
   fix, one row down. `order` below settles it: the mark is drawn immediately BEFORE the chip, the
   chip keeps `margin-left: auto`, and the chip's right gap is 0 on every row. Measured with the
   mark on every other row: row height unchanged (98px marked and unmarked), nothing overflows, the
   chip's top is identical (33px). The mark still carries its title/tooltip and its value.
   ================================================================================================ */

#rail-body .entry-meta > .owner-mini,
#rail-body .home-card-meta > .owner-mini,
#stage .home-card-meta > .owner-mini {
  margin-left: auto;
}

/* ================================================================================================
   THE CHANGED-HANDS MARK GOES BESIDE THE CHIP, NOT OUTSIDE IT (2026-09-24) - APPENDED, never
   inserted mid-file.

   `.case-share-mini` (web/case_share.js) is appended into the same meta container as the holder
   chip, so it is the row's LAST item and `margin-left: auto` on the chip pushes the chip in by the
   mark's width on exactly the rows that carry one. On a mixed list - one job in the list changed
   hands - that is the same defect as the one this file already fixes: the same fact in two places
   depending on the row. Measured on the deployed rail with the mark on every other row: chip right
   gap 41px on marked rows, 0 on unmarked.

   `order` settles it with no DOM change and no height cost. The mark is ordered 1 and the chip 2,
   so the mark paints immediately to the chip's left while the chip keeps its `margin-left: auto`
   and stays flush right. Marked and unmarked rows then share one chip column (right gap 0) and one
   row height (98px at the desk), nothing overflows, and the mark keeps its own title/tooltip.

   Scoped to the same three containers as the rule above, and to these two classes alone.
   ================================================================================================ */

#rail-body .entry-meta > .case-share-mini,
#rail-body .home-card-meta > .case-share-mini,
#stage .home-card-meta > .case-share-mini {
  order: 1;
}

#rail-body .entry-meta > .owner-mini,
#rail-body .home-card-meta > .owner-mini,
#stage .home-card-meta > .owner-mini {
  order: 2;
}

/* ================================================================================================
   THE GREY BOX AROUND ACCOUNT SETTINGS IS GONE (2026-09-25). APPENDED at the tail — never inserted
   mid-file — and scoped to `#settings-overlay.st-own-view`, so nothing else in the console moves.

   Kazim: «Also in account settings there is a gray coloured outline of a box around the whole
   thing, get rid of that its ugly». FOUND BY MEASURING, NOT BY GUESSING. The panel was rendered at
   the operator's own 2541x1285 and every element between the panel root and `.st-account` was walked
   for a visible border / outline / box-shadow, then the FRAME ITSELF was read back in PIXELS — a
   scan of the capture for a column/row that is grey down most of its run (which is what a box edge
   is and what a field's own border is not). MEASURED, before this rule:

     * `#account-card.as-card` — `border: 0.909091px solid rgb(203,209,207)`, box
       x 743..1798, y 117..1131 — A FULL RECTANGLE around the whole account form. A pixel scan of
       `tools/out/access-fixes/account-before.png` found its left edge running x=743 and its right
       edge x=1798 for 0.97 of the panel's height, and its top edge y=117 for 0.92 of the panel's
       width. That is the box the operator is looking at.
     * `#cred-card.cred-card` — the same `border: 0.909091px solid rgb(203,209,207)`, a second
       rectangle around the password / sign-in-name half, and it reads as one box with the first.
     * `#settings-overlay .form-overlay-body` — `border: 0.909091px solid rgb(223,227,226)` on all
       four sides. That one is the SHELL's own edge, and it STAYS: the panel must keep its frame and
       its scrim (Kazim's page-sized landscape pop-up), or the fix undoes the shape he asked for.

   WHY THESE TWO RULES EXIST AND WHAT THEY ARE: `account_settings.js` and `account_credentials.js`
   state the hairline on `.as-card` / `.cred-card` for the MODAL they were written as — "on a dark
   ground a card with no edge reads as a hole rather than a surface" — and each panel already
   overrides its own modal dress for the inline mount (`#account-overlay.as-inline .as-card
   { box-shadow: none; padding: 0; border-radius: 0 }` — note the border the inline mount did NOT
   clear). Inside Settings there is no ground to separate from: the card sits on the shell's own
   slab, so its frame is a box drawn around content rather than a card's edge, which is exactly the
   complaint. Only the FRAME goes.

   THE FILL GOES WITH THE FRAME, AND IT IS THE SAME DEFECT IN DARK (Kazim, 2026-09-25: «in dark mode
   as you can see the screenshot, it has this weird different coloured box around the body and
   everything in account settings, look at the light mode (also a screenshot), it doesn't have that,
   so can you make it the same style as light mode, just in dark, remove the gray box thing»).

   MEASURED with `getComputedStyle` on both themes, the pane open, the card's fill against the body
   slab it sits on:

     light   card rgb(255,255,255)  on slab rgb(251,252,251)  -> delta [4,3,4],  luminance gap  3.28
     dark    card rgb(35,40,48)     on slab rgb(28,32,36)     -> delta [7,8,12], luminance gap  8.08

   The fill is `--raised` — `#ffffff` in light, `#232830` in dark — a MODAL's surface, on top of a
   body that is already `--surface`. In light the two are three points apart and read as one page,
   which is why no one ever saw it; in dark the card is 2.5x further from its ground AND shifts
   BLUE (the +12 on the blue channel against +7/+8 on red/green), so the rectangle is plainly
   visible. That is precisely the asymmetry the operator photographed.

   SO THE FILL BECOMES `transparent`: the pane's own surface shows through and the account form
   reads as ONE page in both themes — the same style, not a dark-only exception. The fields and
   buttons inside KEEP their own `--raised` fill and their own border, which is what still makes a
   control read as a control (light: `#fff` fields on the slab, dark: `#232830` fields on the slab,
   each carried by its border) — the same relationship in both themes, and it is the relationship
   the light theme already had and the operator approved.

   WHAT IS DELIBERATELY NOT TOUCHED — these are the controls and the stated rules a person needs,
   and removing them is not what «a box around the whole thing» means:
     * the fields and buttons inside the form (`.inf-input`, `.btn`) keep their own borders;
     * `#account-twofa`, `.as-state`, `#cred-password-section` and `.cred-sec` keep theirs: those are
       SECTION frames grouping one subject, not a box around the whole panel;
     * `.as-no` / the `.ts-panel` / `.rc-panel` keep theirs — `ts-own`'s is a real state panel and
       `rc-own`'s is a DASHED border, a different mark stating a different fact.
   The rule is scoped to the account view's own class and to these two card elements alone, so no
   other overlay, and no field, moves by a pixel.
   ================================================================================================ */

#settings-overlay.st-own-view #account-card.as-card,
#settings-overlay.st-own-view #cred-card.cred-card {
  border: 0;
  background: transparent;
}

/* ====================================================== THE JOB'S OWN PROCESS, ON EVERY SCREEN

   The operator's report: "after putting the document in and naming the job and document it just
   goes to This Job page you literally did nothing else." The creation step produced a record and no
   process. This is the process, drawn above whichever panel is showing: the job's steps in the
   order they are walked, a tick only where the record carries the evidence, and ONE next act with a
   button that goes to it.

   It is a STRIP, not a rail that gates: nothing here is disabled, no step is blocked behind
   another, and the tabs stay the operator's (the old rail was the thing that gated, and it was
   removed for saying "Approve" and "Send" at a form). This only SAYS where the job is and what is
   next; the operator can ignore it and click any tab. */

.inf-progress {
  border: 1px solid var(--line); border-radius: 4px; padding: var(--s3);
  margin: 0 0 var(--s3); background: var(--sunken, rgba(0,0,0,.03)); min-width: 0;
}
.inf-prog-head { display: flex; gap: var(--s2); align-items: baseline; justify-content: space-between;
                 margin: 0 0 var(--s2); flex-wrap: wrap; }
.inf-prog-count { font-weight: 620; font-size: 0.82em; }
.inf-prog-quiet { color: var(--ink-4); font-size: 0.76em; }
.inf-prog-steps { list-style: none; margin: 0 0 var(--s2); padding: 0;
                  display: flex; flex-direction: column; gap: 2px; }
.inf-prog-step { display: flex; gap: var(--s2); align-items: baseline; flex-wrap: wrap;
                 padding: 3px 6px; border-radius: 3px; color: var(--ink-3); font-size: 0.86em; }
.inf-prog-step.done { color: var(--ink-2); }
.inf-prog-step.here { background: var(--surface); color: var(--ink); font-weight: 600;
                      box-shadow: inset 2px 0 0 var(--accent, #2f6f4f); }
.inf-prog-n { width: 1.4em; text-align: center; color: var(--ink-4); font-size: 0.9em;
              flex: none; }
.inf-prog-step.done .inf-prog-n { color: var(--accent, #2f6f4f); }
.inf-prog-title { min-width: 0; overflow-wrap: anywhere; }
.inf-prog-now { font-size: 0.72em; text-transform: uppercase; letter-spacing: .04em;
                color: var(--accent, #2f6f4f); border: 1px solid currentColor; border-radius: 2px;
                padding: 0 3px; }
.inf-prog-next { display: flex; gap: var(--s3); align-items: center; justify-content: space-between;
                 flex-wrap: wrap; padding-top: var(--s2); border-top: 1px solid var(--line-2); }
.inf-prog-what { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 22em; }
.inf-prog-what b { font-size: 0.9em; }
.inf-prog-what span { color: var(--ink-3); font-size: 0.82em; overflow-wrap: anywhere; }
.inf-prog-done { margin: var(--s2) 0 0; }
@media (max-width: 640px) {
  .inf-prog-next { flex-direction: column; align-items: stretch; }
  .inf-prog-next .btn { width: 100%; }
}
.inf-prog-filled { color: var(--ink-4); font-size: 0.76em; }
/* THE NEXT STEP IS MARKED, not called "here": "here" is the screen the operator is on, and the next
   step is a statement about the JOB. Two different facts, two different marks. */
.inf-prog-step.next { color: var(--ink-2); }
.inf-prog-step.next .inf-prog-n { color: var(--accent, #2f6f4f); font-weight: 700; }

/* ================================================================================================
   THE FIRST PAINT IS NOT A CASE (Kazim, 2026-09-25) — APPENDED at the tail, never inserted mid-file.

   Kazim, on a phone: *"when I have not been on the tab and then I go back to it, doesn't matter what
   part of the mission control I am on it still gets this 'Cause list empty' for a second or 2."*

   FOUND BY RENDERING THE FIRST PAINT, NOT BY READING THE CODE. `#case-id`="no case selected" and
   `#case-name`="Cause list is empty" are LITERAL markup in index.html, and `.case-head` is hidden
   only by `body.on-home` — a class **app.js adds**. app.js is ~950KB, so on a phone there is a real
   window in which the browser has painted index.html and app.js has not yet run. Reproduced exactly
   by serving index.html with the app.js tag stripped: the back chevron, "no case selected", "Cause
   list is empty", the 1-5 step rail and "Agent activity" — the screenshot Kazim sent.

   "Doesn't matter what part I am on" is the tell: this is not a failed fetch or a bad hash, it is
   EVERY fresh load, phone and desk alike.

   THE FIX IS IN THE MARKUP'S OWN DEFAULT STATE, not in a render path. The page ships `mc-booting` on
   <html> (set at parse, before any stylesheet or script), and this block removes the case chrome
   while it stands. That is the honest rendering: the console has not read anything yet, so it must
   not claim a case whose name is a placeholder. app.js drops `mc-booting` as its first act, so every
   state after that is app.js's to draw, exactly as before.

   WHY THE MARKUP TEXT IS NOT REWORDED: `verify_idle_tab_recovers.py` uses "Cause list is empty" as
   the marker of the UN-RENDERED state (E15 and `verify_case_race.py` rely on that meaning), so the
   string stays. What changes is that the un-rendered state no longer SHOWS it as a case title. */
html.mc-booting .case-head,
html.mc-booting .stepper,
html.mc-booting .step-caption,
html.mc-booting .chat-wrap { display: none; }
/* the stage holds nothing yet either, and an empty stage beside empty chrome is what made the shell
   read as a broken case */
html.mc-booting .sheet-scroll { overflow-y: hidden; }

/* ------------------------------------------------------------------------------------------------
   THE BOOT NOTE — the only thing the console says between the first paint and app.js's first act.

   It is visible ONLY while `html.mc-booting` stands (see the block above). At rest both lines are
   hidden and it takes no space, so no render path ever sees it. While booting it replaces the
   placeholder case title with the truth, and `mc-boot-stalled` — added by index.html's failsafe when
   app.js has not arrived in 8s — swaps the quiet line for a sentence that says the load did not
   finish. R8: a stage that cannot resolve must SAY so; it must not dress itself as an empty case.

   Tokens first, no new colours: it reads at the console's own quiet weight. Motion is a fade only,
   and it is inside the one authored moment (a state arriving), not a new one. */
html.mc-booting .boot-note { display: block; }
.boot-note { display: none; }
.boot-note-loading { display: inline; }
.boot-note-stalled { display: none; }
html.mc-boot-stalled .boot-note-loading { display: none; }
html.mc-boot-stalled .boot-note-stalled { display: inline; }

.boot-note {
  margin: 0;
  padding: 22px 4px 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-2, #6b7280);
  animation: boot-note-in 260ms cubic-bezier(.22, .61, .36, 1) both;
}
.boot-note em { font-style: normal; }
html.mc-boot-stalled .boot-note { color: var(--warn-ink, #92400e); }
@keyframes boot-note-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .boot-note { animation: none; }
}
