/* The board's stylesheet, ported WHOLE from the prototype.
 *
 * `docs/prototypes/board/index.html` lines 10-655, unchanged. That markup and
 * this stylesheet came from clinic review, and the reviewed thing is the one
 * to port -- selecting from it would make the Python board a redesign nobody
 * approved, and a changed layout cannot be compared against what was agreed.
 *
 * Rules for selectors this board does not yet use (modals, the criteria
 * editor, the roster forms) are kept rather than pruned. They cost bytes;
 * removing them costs the ability to diff this file against its source.
 */

  :root {
    --bg:        #f6f7f6;
    --surface:   #ffffff;
    --surface-2: #eef1ef;
    --ink:       #171c1a;
    --ink-2:     #4a5551;
    --muted:     #78837e;
    --line:      #d9dedb;
    --line-soft: #e7ebe9;
    --accent:    #2e6a5c;
    --accent-ink:#ffffff;
    --accent-soft: rgba(46,106,92,.10);
    --gap:       #a8471f;
    --gap-soft:  rgba(179,83,44,.11);
    --gap-line:  rgba(179,83,44,.55);
    --caution:   #8a6720;
    --caution-soft: rgba(151,114,42,.13);
    --ok:        #35704c;
    --ok-soft:   rgba(63,122,86,.12);
    /* Advisory is its OWN hue, deliberately not amber: --caution is already
       amber and already means "deprioritised" on the same card. A warn that
       borrows it says the wrong sentence, and so does one that borrows the
       blocked red. Blue reads as informational against both. */
    --advisory:  #2f6690;
    --advisory-soft: rgba(47,102,144,.12);
    --lunch-soft: rgba(46,106,92,.055);
    --shadow:    0 1px 2px rgba(23,28,26,.06), 0 8px 28px rgba(23,28,26,.10);
    --radius:    3px;
    --ui: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
            "Liberation Mono", monospace;
  }

  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
      --bg:#121614; --surface:#1a201d; --surface-2:#222a26;
      --ink:#e8edea; --ink-2:#b3bdb8; --muted:#8c968f;
      --line:#2f3a35; --line-soft:#262f2b;
      --accent:#74b8a4; --accent-ink:#0e1412; --accent-soft:rgba(116,184,164,.14);
      --gap:#e6926a; --gap-soft:rgba(224,138,95,.15); --gap-line:rgba(224,138,95,.6);
      --caution:#d7ad55; --caution-soft:rgba(212,169,78,.15);
      --ok:#79c194; --ok-soft:rgba(121,193,148,.14);
      --advisory:#7fb2d4; --advisory-soft:rgba(127,178,212,.16);
      --lunch-soft:rgba(116,184,164,.06);
      --shadow:0 1px 2px rgba(0,0,0,.4), 0 10px 34px rgba(0,0,0,.5);
    }
  }
  :root[data-theme="dark"] {
    --bg:#121614; --surface:#1a201d; --surface-2:#222a26;
    --ink:#e8edea; --ink-2:#b3bdb8; --muted:#8c968f;
    --line:#2f3a35; --line-soft:#262f2b;
    --accent:#74b8a4; --accent-ink:#0e1412; --accent-soft:rgba(116,184,164,.14);
    --gap:#e6926a; --gap-soft:rgba(224,138,95,.15); --gap-line:rgba(224,138,95,.6);
    --caution:#d7ad55; --caution-soft:rgba(212,169,78,.15);
    --ok:#79c194; --ok-soft:rgba(121,193,148,.14);
    --advisory:#7fb2d4; --advisory-soft:rgba(127,178,212,.16);
    --lunch-soft:rgba(116,184,164,.06);
    --shadow:0 1px 2px rgba(0,0,0,.4), 0 10px 34px rgba(0,0,0,.5);
  }

  * { box-sizing: border-box; }

  body {
    margin: 0; background: var(--bg); color: var(--ink);
    font-family: var(--ui); font-size: 14px; line-height: 1.45;
    -webkit-font-smoothing: antialiased;
  }

  /* A plain link with no class of its own -- every child's name in the week
     grid's pinned column, most names on /people, the breadcrumb -- had no
     colour of its own either, so the browser painted the default link blue
     (`#0000EE`) over this palette. Against the dark surface that is under
     2:1 contrast, close to invisible (browser pass, Task 9 r1). One
     specificity below every class-styled link on purpose: `a` (0,0,1) loses
     to `.btn` (0,1,0) and the header navigation's `.views a` (0,1,1), so a
     button's own colour still wins. `a:link`/`a:visited` (0,1,1) would beat
     `.btn` and paint a button's text the colour of the button -- do not
     "fix" this into one. */
  a { color: var(--accent); }

  .synthetic-banner {
    background: var(--caution-soft); border-bottom: 1px solid var(--line);
    color: var(--caution); font-family: var(--mono); font-size: 10.5px;
    letter-spacing: .09em; text-transform: uppercase; padding: 5px 18px; text-align: center;
  }

  .bar {
    position: sticky; top: 0; z-index: 30;
    display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
    padding: 12px 18px; background: var(--surface); border-bottom: 1px solid var(--line);
  }
  .brand { display: flex; align-items: baseline; gap: 9px; }
  .brand b { font-size: 15px; font-weight: 640; letter-spacing: -.01em; }
  .brand .date { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }

  .summary { display: flex; align-items: center; gap: 18px; margin-left: auto; }
  .stat { display: flex; align-items: baseline; gap: 6px; }
  .stat .n {
    font-family: var(--mono); font-size: 19px; font-weight: 600;
    font-variant-numeric: tabular-nums; letter-spacing: -.02em;
  }
  .stat .l { font-size: 10.5px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); }
  .stat.is-gap .n { color: var(--gap); }

  .views { display: flex; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
  .views button {
    font: inherit; font-size: 12.5px; padding: 5px 13px;
    background: transparent; color: var(--ink-2); border: 0; cursor: pointer;
    border-right: 1px solid var(--line);
  }
  .views button:last-child { border-right: 0; }
  .views button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
  .views button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
  .views button.step { font-weight: 600; }
  .navgroup { display: flex; flex-direction: column; gap: 3px; }
  .grouplabel {
    font-family: var(--mono); font-size: 9px; letter-spacing: .12em;
    text-transform: uppercase; color: var(--muted); padding-left: 2px;
  }
  .pane { padding: 18px; max-width: 1100px; }
  .pane h2 { margin: 0 0 4px; font-size: 17px; font-weight: 620; letter-spacing: -.012em; }
  .pane .lede { margin: 0 0 14px; font-size: 12.5px; color: var(--muted); max-width: 70ch; }
  .rtabs { display: flex; gap: 6px; margin-bottom: 12px; align-items: center; }
  .rtab {
    font: inherit; font-size: 12.5px; padding: 4px 11px; cursor: pointer;
    background: var(--surface); color: var(--ink-2);
    border: 1px solid var(--line); border-radius: var(--radius);
  }
  .rtab[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
  .roster { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
  .roster-row {
    display: grid; grid-template-columns: 180px 1fr auto; gap: 12px; align-items: center;
    width: 100%; text-align: left; font: inherit; color: inherit; cursor: pointer;
    background: none; border: 0; border-bottom: 1px solid var(--line-soft); padding: 8px 12px;
  }
  .roster-row:last-child { border-bottom: 0; }
  .roster-row:hover { background: var(--surface-2); }
  .roster-row .nm { font-weight: 560; }
  .roster-row .nm.code { font-family: var(--mono); letter-spacing: .05em; }
  .roster-row .meta, .roster-row .go { font-family: var(--mono); font-size: 10.5px; color: var(--muted); }
  /* The profile editor is a modal now (see renderProfileForm): a drawer
     appended after the roster only ever worked because .roster was a short
     scroll box. Its .setgrid needs room inside the card. */
  .modal .setgrid { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
  .ck { display: flex; align-items: center; gap: 6px; font-size: 12.5px; }
  .readby { margin: 4px 0 0; font-family: var(--mono); font-size: 9.5px; color: var(--muted); }
  .readby.none { opacity: .45; }
  .readby code { background: var(--surface); border-radius: 2px; padding: 0 3px; }
  .strip { display: flex; gap: 2px; margin-top: 3px; }
  .strip .wd {
    font-family: var(--mono); font-size: 8.5px; line-height: 1.5; width: 12px; text-align: center;
    border: 1px solid var(--line); border-radius: 2px; color: var(--muted);
  }
  .strip .wd[data-on="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
  .board td .cell { display: flex; align-items: center; gap: 5px; }
  .board td .cell select { font: inherit; font-size: 12px; max-width: 150px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); color: var(--ink); padding: 2px 4px; }
  .drop { font: inherit; font-size: 11px; line-height: 1; cursor: pointer; color: var(--muted); background: none; border: 1px solid var(--line); border-radius: 2px; padding: 2px 5px; }
  .drop:hover { color: var(--gap); border-color: var(--gap-line); }
  .schedstat { margin-left: auto; align-self: center; font-family: var(--mono); font-size: 10.5px; color: var(--muted); }
  .schedstat[data-warn="true"] { color: var(--gap); }
  .warnbar {
    background: var(--caution-soft); border-bottom: 1px solid var(--line);
    color: var(--caution); font-size: 12px; padding: 8px 18px;
  }
  .promote {
    font: inherit; font-family: var(--mono); font-size: 9px; letter-spacing: .05em;
    margin-left: 5px; cursor: pointer; color: var(--accent);
    background: none; border: 1px solid var(--accent); border-radius: 2px; padding: 0 4px;
  }
  .promote:hover { background: var(--accent-soft); }
  .views .seq {
    display: flex; align-items: center; padding: 0 8px;
    font-family: var(--mono); font-size: 11px; color: var(--muted);
    background: var(--surface-2); border-right: 1px solid var(--line);
    user-select: none;
  }

  .gear {
    font: inherit; font-size: 12.5px; padding: 5px 12px; cursor: pointer;
    background: transparent; color: var(--ink-2);
    border: 1px solid var(--line); border-radius: var(--radius);
  }
  .gear:hover { background: var(--surface-2); }
  .gear[aria-expanded="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
  .gear:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

  /* ---------- settings ---------- */
  .settings { border-bottom: 1px solid var(--line); background: var(--surface-2); padding: 14px 18px 16px; }
  .settings h3 { margin: 0 0 3px; font-size: 12.5px; font-weight: 620; }
  .settings .lede { margin: 0 0 12px; font-size: 12px; color: var(--muted); max-width: 62ch; }
  .setgrid { display: grid; gap: 14px 26px; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); max-width: 1000px; }
  .setfield label {
    display: block; font-family: var(--mono); font-size: 9.5px;
    letter-spacing: .09em; text-transform: uppercase; color: var(--muted); margin-bottom: 4px;
  }
  .setfield select, .setfield input[type="text"] {
    font: inherit; font-size: 12.5px; width: 100%; padding: 4px 7px; color: var(--ink);
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  }
  .setfield select:focus-visible, .setfield input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
  .setfield .req {
    font-size: 8.5px; letter-spacing: .1em; color: var(--gap);
    border: 1px solid var(--gap-line); border-radius: 2px; padding: 0 3px; margin-left: 4px;
  }
  .setfield .hint { margin: 5px 0 0; font-size: 11px; color: var(--muted); }
  .setfield .hint.warn { color: var(--gap); }
  .setfield .hint code { font-family: var(--mono); background: var(--surface); border-radius: 2px; padding: 0 3px; }
  .settings .err { margin: 10px 0 0; font-size: 12px; color: var(--gap); }
  /* Same treatment on the criteria screen: a set that will not load has to
     look like a refusal, not like a note. */
  .criteria .err {
    margin: 10px 0 0; font-size: 12px; color: var(--gap); line-height: 1.6;
    border: 1px solid var(--gap-line); border-radius: var(--radius);
    background: var(--gap-soft); padding: 8px 10px;
  }

  /* ---------- absences ---------- */
  .abs-intro { padding: 16px 18px 0; max-width: 92ch; }
  .abs-intro h2 { margin: 0 0 4px; font-size: 17px; font-weight: 620; letter-spacing: -.012em; }
  .abs-intro p { margin: 0 0 4px; font-size: 12.5px; color: var(--muted); }

  .abs-cols {
    display: grid; gap: 18px; padding: 14px 18px 20px;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); max-width: 1100px;
  }
  .abs-panel { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
  .abs-head {
    padding: 9px 12px; border-bottom: 1px solid var(--line); background: var(--surface-2);
    display: flex; align-items: baseline; justify-content: space-between;
  }
  .abs-head h3 { margin: 0; font-size: 12.5px; font-weight: 620; }
  .abs-head .count { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }

  .abs-search { padding: 10px 12px; border-bottom: 1px solid var(--line-soft); position: relative; }
  .abs-search input {
    font: inherit; font-size: 13px; width: 100%; padding: 6px 9px; color: var(--ink);
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  }
  .abs-search input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
  .hits { margin-top: 6px; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
  .dayframe { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .dayframe .daydate { font-family: var(--mono); font-size: 12px; font-weight: 600; }
  .dayframe .daynote { font-family: var(--mono); font-size: 11.5px; color: var(--caution); }
  .dayframe .daypick { font-size: 11px; color: var(--muted); }
  /* A previewed weekday has no "now" to report. */
  .dayframe[data-daymode="preview"] .clocknow { display: none; }
  .clocknow { font-family: var(--mono); font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }
  .clockzone { font-family: var(--mono); font-size: 9px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
  .clockzone[data-elsewhere="true"] { color: var(--caution); }
  .board td[data-vacancy="true"] { box-shadow: inset 2px 0 0 var(--caution); }
  .board td[data-vacancy="true"] .note { color: var(--caution); }
  .board td[data-phase="past"] { opacity: .55; }
  .board td[data-phase="past"] .cell { cursor: default; }
  .board td[data-phase="now"] { box-shadow: inset 0 2px 0 var(--accent); }
  .chip[data-elapsed="true"] { opacity: .4; }
  .hitacts { display: flex; gap: 5px; }
  .hitacts button {
    font: inherit; font-family: var(--mono); font-size: 10px; letter-spacing: .05em;
    cursor: pointer; padding: 2px 7px; color: var(--ink-2);
    background: var(--surface); border: 1px solid var(--line); border-radius: 2px;
  }
  .hitacts button:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
  .hit {
    display: flex; justify-content: space-between; align-items: center; gap: 10px;
    width: 100%; text-align: left; font: inherit; font-size: 12.5px; color: inherit;
    background: var(--surface); border: 0; border-bottom: 1px solid var(--line-soft);
    /* A row, not a control, now that it carries two buttons of its own --
       the whole-row pointer and hover said "click me" for a click that no
       longer does anything. */
    padding: 6px 10px; cursor: default;
  }
  .hit:last-child { border-bottom: 0; }
  .hit .nm { font-weight: 560; }
  .hit .nm.code { font-family: var(--mono); letter-spacing: .05em; }
  .hit .meta { font-family: var(--mono); font-size: 10.5px; color: var(--muted); }

  .abs-list { padding: 4px 0 6px; }
  .abs-row {
    display: flex; align-items: center; gap: 10px; justify-content: space-between;
    padding: 7px 12px; border-bottom: 1px solid var(--line-soft);
  }
  .abs-row:last-child { border-bottom: 0; }
  .abs-row .nm { font-weight: 560; font-size: 13px; }
  .abs-row .nm.code { font-family: var(--mono); font-size: 13.5px; letter-spacing: .06em; }
  .chips { display: flex; gap: 4px; }
  .chip {
    font: inherit; font-family: var(--mono); font-size: 10px; letter-spacing: .05em;
    text-transform: uppercase; padding: 2px 7px; cursor: pointer;
    border: 1px solid var(--line); border-radius: 2px;
    background: var(--surface); color: var(--muted);
  }
  .chip[data-out="true"] {
    background: var(--gap-soft); border-color: var(--gap-line); color: var(--gap);
    text-decoration: line-through;
  }
  .chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
  .abs-empty { color: var(--muted); font-size: 12.5px; padding: 18px 12px; text-align: center; }

  .handoff {
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
    padding: 13px 18px; margin: 0 18px 24px;
    border: 1px solid var(--line); border-left: 3px solid var(--accent);
    border-radius: var(--radius); background: var(--surface-2); max-width: 1064px;
  }
  .handoff .said { font-size: 12.5px; color: var(--ink-2); }
  .handoff .said b { font-family: var(--mono); font-variant-numeric: tabular-nums; }

  /* ---------- grid ---------- */
  /* NO INNER SCROLLER. This was a real vertical scroll container, and a
     scrollview inside the page scrollview is the thing to avoid: on a
     touchscreen the inner box swallows the gesture and the page will not
     move. The grid now grows to its own height and the PAGE scrolls, which
     also means the sticky header below measures `top` from the viewport
     rather than from here -- see its rule. */
  .scroll { overflow: visible; }
  table.board { border-collapse: separate; border-spacing: 0; width: 100%; min-width: 820px; font-variant-numeric: tabular-nums; }
  .board th, .board td { border-bottom: 1px solid var(--line-soft); border-right: 1px solid var(--line-soft); padding: 0; text-align: left; vertical-align: middle; }
  .board thead th {
    /* --bar-h, because the sticky context is now the VIEWPORT: .scroll
       stopped being a scroll container, so this has to clear the page's own
       sticky bar rather than sitting at its container's top. Zero was right
       only while an inner scroller existed, and would now park the header
       under the bar where nothing can read it. */
    position: sticky; top: var(--bar-h, 71px); z-index: 20; background: var(--surface-2);
    border-bottom: 1px solid var(--line); font-size: 10.5px; font-weight: 600;
    text-transform: uppercase; letter-spacing: .07em; color: var(--muted);
    padding: 7px 11px; white-space: nowrap;
  }
  .board thead th .sub { display: block; font-family: var(--mono); font-size: 10px; letter-spacing: 0; text-transform: none; opacity: .8; }
  .board th.rowhead, .board td.rowhead {
    position: sticky; left: 0; z-index: 10; background: var(--surface);
    border-right: 1px solid var(--line); padding: 7px 12px; min-width: 168px; max-width: 200px;
  }
  .board thead th.rowhead { z-index: 25; background: var(--surface-2); }
  .board th.lunchcol, .board td.lunchcol { background: var(--lunch-soft); }
  .board thead th.lunchcol { background: var(--surface-2); }
  /* The Schedule band has set this attribute since the day it was built and
     nothing ever drew it, so "this group has no standing therapist" -- the
     one thing authoring exists to fix -- looked exactly like a finished row. */
  .board td.lunchcol[data-warn="true"] { background: var(--caution-soft); box-shadow: inset 3px 0 0 var(--caution); }
  .board td.lunchcol[data-warn="true"] .cell::after {
    content: "no therapist"; display: block;
    font-family: var(--mono); font-size: 9.5px; letter-spacing: .04em; color: var(--caution);
  }

  .who { font-weight: 560; font-size: 13px; }
  .who.code { font-family: var(--mono); font-size: 14px; font-weight: 640; letter-spacing: .06em; }
  .who.away { text-decoration: line-through; opacity: .55; }
  .tags {
    display: flex; gap: 5px; flex-wrap: wrap; margin-top: 2px;
    font-family: var(--mono); font-size: 9.5px; letter-spacing: .04em;
    text-transform: uppercase; color: var(--muted);
  }
  .tags .t { border: 1px solid var(--line); border-radius: 2px; padding: 0 3px; }
  .tags .t.tri { color: var(--accent); border-color: var(--accent); }
  .tags .t.int { color: var(--gap); border-color: var(--gap-line); }
  .tags .t.out { color: var(--gap); border-color: var(--gap-line); }

  .cell { display: block; width: 100%; text-align: left; font: inherit; background: none; border: 0; padding: 8px 11px; min-height: 46px; cursor: default; color: inherit; }
  .cell .init { font-family: var(--mono); font-size: 13px; font-weight: 600; letter-spacing: .03em; }
  .cell .init.nm { font-family: var(--ui); font-size: 12.5px; font-weight: 560; letter-spacing: -.005em; }
  .cell .note { font-size: 10.5px; color: var(--muted); }

  td.is-empty .cell { color: var(--muted); }
  td.is-empty .cell .init { font-weight: 400; opacity: .5; }
  td.is-away { background: var(--surface-2); }
  td.is-away .cell .init { font-weight: 400; opacity: .6; text-decoration: line-through; }

  td.is-gap { background: var(--gap-soft); box-shadow: inset 3px 0 0 var(--gap); }
  td.is-gap .cell { cursor: pointer; color: var(--gap); }
  td.is-gap .cell .init { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }
  td.is-gap .cell:hover { background: rgba(179,83,44,.09); }
  td.is-gap .cell:focus-visible { outline: 2px solid var(--gap); outline-offset: -2px; }

  td.is-caution { background: var(--caution-soft); box-shadow: inset 3px 0 0 var(--caution); }
  td.is-caution .cell { color: var(--caution); }
  td.is-free { background: var(--ok-soft); box-shadow: inset 3px 0 0 var(--ok); }
  td.is-free .cell { color: var(--ok); }

  tr:hover td.rowhead { background: var(--surface-2); }

  /* ---------- worklist ---------- */
  .worklist { padding: 16px 18px 40px; max-width: 760px; }
  .wl-item {
    display: grid; grid-template-columns: 88px 1fr auto; gap: 14px; align-items: center;
    width: 100%; text-align: left; font: inherit; color: inherit;
    background: var(--surface); border: 1px solid var(--line); border-left: 3px solid var(--gap);
    border-radius: var(--radius); padding: 11px 13px; margin-bottom: 8px; cursor: pointer;
  }
  /* Written for the prototype's <button>, which needs no underline reset of
     its own. An open gap row is an <a> now (Task 6), and an anchor brings
     one this block never had to undo before -- the same fix `a.chip` and
     `a.btn` already carry below. */
  a.wl-item { text-decoration: none; }
  .wl-item:hover { background: var(--surface-2); }
  .wl-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
  .wl-time { font-family: var(--mono); font-size: 12px; font-weight: 600; color: var(--gap); font-variant-numeric: tabular-nums; }
  /* A lunch gap is not a session gap. It arrived in the same worklist wearing
     the same red left border, which said "a session went uncovered" about a
     table of children waiting to eat. Lunch has its own hue everywhere else on
     this board (--lunch-soft on the band, the accent on the column); the
     worklist is the one place that had not been told. */
  .wl-item.wl-lunch { border-left-color: var(--accent); background: var(--lunch-soft); }
  .wl-item.wl-lunch:hover { background: var(--accent-soft); }
  .wl-item.wl-lunch .wl-time { color: var(--accent); }
  .wl-cta { font-family: var(--mono); font-size: 11px; color: var(--muted); }
  .wl-cta[data-costly="true"] { color: var(--caution); }
  /* A gap that has passed is a record, not work: greyed, no pointer, and
     its left border drops to the neutral line so a scan down the list stops
     at the ones that can still be filled. */
  .wl-item[data-passed="true"] {
    opacity: .55; cursor: default; border-left-color: var(--line);
    background: var(--surface-2);
  }
  .wl-item[data-passed="true"] .wl-time { color: var(--muted); }
  .wl-item[data-passed="true"] .wl-cta {
    text-transform: uppercase; letter-spacing: .07em; font-size: 10px;
  }
  .wl-empty { color: var(--muted); padding: 30px 0; text-align: center; }

  /* ---------- modal ---------- */
  /* The OVERLAY scrolls, not the card inside it. A tall candidate list used
     to scroll within .modal while .scrim held it centred -- an inner
     scroller again, and the one most likely to be met on a phone. */
  .scrim { position: fixed; inset: 0; z-index: 50; background: rgba(10,14,12,.55); display: flex; align-items: flex-start; justify-content: center; padding: 20px; overflow-y: auto; }
  @supports (backdrop-filter: blur(2px)) { .scrim { backdrop-filter: blur(2px); } }
  /* `<dialog class="gap-modal">` (Task 10), not `.scrim` -- `showModal()`
     already gives it top layer, centring and an inert background, so this
     only cancels the UA stylesheet's own border/padding/background (which
     would double the `.modal` card it wraps) and repaints `::backdrop`,
     which ships transparent in some browsers and only a faint default tint
     in others. `max-*`/`overflow-y` keep a tall ladder scrolling inside the
     dialog rather than the page, same as `.scrim` did for the div it used to
     wrap. */
  .gap-modal { border: none; padding: 0; background: transparent; color: inherit; max-width: min(560px, calc(100% - 40px)); max-height: calc(100% - 40px); overflow-y: auto; }
  .gap-modal::backdrop { background: rgba(10,14,12,.55); }
  @supports (backdrop-filter: blur(2px)) { .gap-modal::backdrop { backdrop-filter: blur(2px); } }
  .modal { background: var(--surface); border: 1px solid var(--line); border-radius: 5px; box-shadow: var(--shadow); width: min(560px, 100%); margin: auto; }
  .modal-head { padding: 15px 18px 13px; border-bottom: 1px solid var(--line); }
  .pager { display: flex; align-items: center; justify-content: space-between; font-family: var(--mono); font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); margin-bottom: 7px; }
  .pager .nav { display: flex; gap: 5px; }
  .pager button { font: inherit; font-size: 11px; background: transparent; color: var(--ink-2); border: 1px solid var(--line); border-radius: 2px; padding: 1px 8px; cursor: pointer; }
  .pager button:hover:not(:disabled) { background: var(--surface-2); }
  .pager button:disabled { opacity: .35; cursor: default; }
  /* The worklist pager (Task 3, `M40`), rendered by `_modal_shell.html` for
     both families. `.pager` above is the prototype's own transliteration and
     is BUTTON-based (`.pager button`) with a `.nav` wrapper around the pair
     of controls -- this markup is anchor-and-span (a real link so a pager
     arrow is a plain navigation, never an `hx-get` inside an open dialog) and
     has no `.nav` wrapper, so none of `.pager`'s own selectors match it. Its
     LAYOUT (flex, space-between, the mono/uppercase position readout) is
     reused verbatim on `.modal-pager`/`.pos`; `.pager button:disabled`'s
     dimming is reused on `.btn.is-off`, the disabled-end span. The controls
     themselves need no new rules at all: `.btn`/`a.btn` (below) already style
     both the `<a>` and the disabled `<span>`. */
  .modal-pager { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 7px; }
  .modal-pager .pos { font-family: var(--mono); font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
  .modal-pager .btn.is-off { opacity: .35; cursor: default; }
  .modal-head h2 { margin: 0; font-size: 19px; font-weight: 620; letter-spacing: -.015em; text-wrap: balance; }
  .modal-head h2.code { font-family: var(--mono); letter-spacing: .05em; }
  .constraints { display: flex; gap: 7px; flex-wrap: wrap; margin-top: 8px; font-family: var(--mono); font-size: 10.5px; color: var(--ink-2); }
  .constraints span { border: 1px solid var(--line); border-radius: 2px; padding: 1px 6px; background: var(--surface-2); }
  .constraints span.hot { color: var(--gap); border-color: var(--gap-line); background: var(--gap-soft); }
  .modal-body { padding: 13px 18px 16px; }
  .rung-label { font-family: var(--mono); font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin: 13px 0 6px; }
  .rung-label:first-child { margin-top: 0; }
  /* --- criteria screen --- */
  .criteria { margin-bottom: 18px; }
  .criteria h3 { margin: 0 0 4px; font-size: 15px; }
  .criteria .ver { font-family: var(--mono); font-size: 12px; color: var(--muted); font-weight: 400; }
  .rulecard {
    border: 1px solid var(--line); border-left: 3px solid var(--accent);
    border-radius: var(--radius); background: var(--surface);
    padding: 10px 12px; margin-top: 10px;
  }
  .rulehead { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
  .rulehead code { font-family: var(--mono); font-size: 12px; color: var(--ink-2); }
  .sev {
    font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em;
    padding: 1px 6px; border-radius: 2px; font-weight: 600;
  }
  .sev-block { background: var(--gap-soft); color: var(--gap); }
  /* Advisory shares the warn card's hue, so one severity reads the same
     wherever it appears -- on a rule card here and on a candidate there. */
  .sev-warn { background: var(--advisory-soft); color: var(--advisory); }
  .ruleclause { margin: 0 0 4px; font-size: 13px; line-height: 1.5; }
  .rulemsg { margin: 0; font-size: 12.5px; color: var(--ink-2); font-style: italic; }
  .criteria .readby { margin: 6px 0 0; font-size: 11.5px; color: var(--muted); }
  .impact { margin: 8px 0 0; padding-left: 16px; font-size: 12px; color: var(--ink-2); line-height: 1.7; }
  .impact b { color: var(--ink); font-variant-numeric: tabular-nums; }
  /* The third figure is the reason this screen exists, so a NON-ZERO third
     figure has to look like a cost rather than like another statistic. */
  .impact .impact-bad { color: var(--gap); }
  .impact .impact-bad b { color: var(--gap); }

  /* --- the save diff --- */
  .savediff { margin-top: 14px; }
  .savediff h4 { margin: 0 0 6px; font-size: 12.5px; }
  .diff {
    font-family: var(--mono); font-size: 11.5px; line-height: 1.55;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface-2); padding: 8px 10px; margin: 0;
    overflow-x: auto; white-space: pre;
  }
  .diff span { display: block; }
  /* Coloured AND signed. The leading -/+ is part of the text, so the diff
     stays readable when the colours are not -- printed, or by a reader who
     cannot separate red from green. */
  .d-del { color: var(--gap); background: var(--gap-soft); }
  .d-add { color: var(--ok); background: var(--ok-soft); }
  .d-ctx { color: var(--ink-2); }

  .cand {
    display: grid; grid-template-columns: 1fr auto; gap: 12px; align-items: start;
    width: 100%; text-align: left; font: inherit; color: inherit;
    background: var(--surface); border: 1px solid var(--line); border-left: 3px solid var(--line);
    border-radius: var(--radius); padding: 9px 11px; margin-bottom: 6px;
  }
  .cand.ok { border-left-color: var(--ok); cursor: pointer; }
  .cand.ok:hover { background: var(--ok-soft); }
  .cand.meh { border-left-color: var(--caution); cursor: pointer; }
  .cand.meh:hover { background: var(--caution-soft); }
  .cand.no { border-left-color: var(--gap); opacity: .72; cursor: not-allowed; }
  /* Assignable, like .ok and .meh -- a warning is not a refusal. */
  .cand.warn { border-left-color: var(--advisory); cursor: pointer; }
  .cand.warn:hover { background: var(--advisory-soft); }
  .cand .who2 { font-size: 13.5px; font-weight: 600; }
  .cand .who2.code { font-family: var(--mono); letter-spacing: .05em; }
  .cand .verdict { font-size: 12px; font-weight: 560; }
  .cand.ok .verdict { color: var(--ok); }
  .cand.meh .verdict { color: var(--caution); }
  .cand.no .verdict { color: var(--gap); }
  .cand.warn .verdict { color: var(--advisory); }
  .cand .reason { font-size: 11.5px; color: var(--muted); margin-top: 1px; }
  .cand .reason code { font-family: var(--mono); font-size: 10.5px; background: var(--surface-2); border-radius: 2px; padding: 0 3px; }
  /* Which child the verdict is about. Muted like the rest of the reason would
     bury the one word that decides where to look next. */
  .cand .reason b { color: var(--ink-2); font-weight: 620; }
  .cand .act { font-family: var(--mono); font-size: 10px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); white-space: nowrap; padding-top: 3px; }
  .none-left { color: var(--muted); font-size: 12.5px; padding: 10px 0; }
  .modal-foot { padding: 11px 18px; border-top: 1px solid var(--line); display: flex; gap: 8px; justify-content: flex-end; background: var(--surface-2); }

  .btn { font: inherit; font-size: 12.5px; padding: 5px 13px; cursor: pointer; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); color: var(--ink); }
  .btn:hover { background: var(--surface-2); }
  .btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
  .btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
  .btn.tiny { font-size: 11.5px; padding: 2px 8px; }
  .btn.danger { color: var(--gap); border-color: var(--gap-line); }

  /* --- the rule builder --- */
  .ruleedit { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line); }
  .ruleedit .setfield { margin-bottom: 8px; }
  .blockedit { margin: 10px 0; }
  .blockedit > label {
    display: block; font-size: 11px; text-transform: uppercase;
    letter-spacing: .06em; color: var(--muted); margin-bottom: 4px;
  }
  .condrow { display: flex; gap: 6px; align-items: center; margin-bottom: 5px; flex-wrap: wrap; }
  .condrow select, .condrow input { font: inherit; font-size: 12.5px; padding: 3px 6px;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface); color: var(--ink); }
  .anybranch { border-left: 2px solid var(--line); padding-left: 8px; margin-bottom: 6px; }
  .anylabel { font-size: 11px; color: var(--muted); font-style: italic; }
  .editactions { display: flex; gap: 8px; margin-top: 10px; }

  .toast { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%); z-index: 60; background: var(--ink); color: var(--bg); font-size: 12.5px; padding: 7px 15px; border-radius: 3px; box-shadow: var(--shadow); }

  .footnote { padding: 18px; border-top: 1px solid var(--line-soft); color: var(--muted); font-size: 11.5px; max-width: 780px; }
  .footnote code { font-family: var(--mono); font-size: 10.5px; background: var(--surface-2); border-radius: 2px; padding: 0 3px; }
  .footnote p { margin: 0 0 9px; }
  .footnote p:last-child { margin-bottom: 0; }

  /* ---------- narrow screens ---------- */
  /* This block used to hide the three grids and print a note saying they were
     desktop views. P4 had ruled that out as settled ("a 90 x 3 grid is a
     desktop artifact and will not travel to a phone; the worklist will"), and
     P38 overrules it: what does not travel is the four-column ARRANGEMENT,
     not the information, so the block now rearranges instead of hiding.

     700px, matching views.js's NARROW_MAX, which picks the opening view. */
  @media (max-width: 700px) {
    /* A ROW BECOMES A CARD. The information in a row fits a phone; the
       four-column arrangement of it does not, which is the whole of what P4
       got wrong. Table boxes cannot stack, so rows and cells stop being
       table boxes -- the markup is unchanged and only its arrangement moves. */
    .scroll { overflow: visible; }
    table.board { display: block; min-width: 0; }
    /* The column headings are on every card line now, so the header row goes
       -- EXCEPT its first cell, which is not a column heading at all: it is
       the day's count ("Client — 34"). Hiding the whole thead would delete a
       figure the desktop board shows and the phone then silently would not. */
    .board thead, .board thead tr, .board thead th.rowhead { display: block; }
    .board thead th:not(.rowhead) { display: none; }
    .board thead th.rowhead {
      position: static; border: 0; border-bottom: 1px solid var(--line);
      padding: 8px 12px; background: transparent;
    }
    .board tbody { display: block; }
    .board tbody tr {
      display: block; margin: 0 0 10px; border: 1px solid var(--line);
      border-radius: var(--radius); background: var(--surface); overflow: hidden;
    }
    .board td, .board td.rowhead {
      display: block; width: auto; max-width: none; min-width: 0;
      position: static;                            /* the sticky row head has no axis here */
      border-right: 0; border-bottom: 1px solid var(--line-soft);
    }
    .board tr td:last-child { border-bottom: 0; }

    /* The row head is the card's title, not one of its labelled lines. */
    .board td.rowhead {
      background: var(--surface-2); border-bottom: 1px solid var(--line);
      padding: 9px 12px;
    }

    /* Every other cell names its column. The name itself is generated into
       #col-labels from columnLabels(); what lives here is only how it looks. */
    .board td:not(.rowhead) { display: flex; align-items: baseline; gap: 10px; }
    .board td:not(.rowhead)::before {
      flex: 0 0 84px; padding: 8px 0 8px 12px;
      font-family: var(--mono); font-size: 9.5px; letter-spacing: .09em;
      text-transform: uppercase; color: var(--muted);
    }
    /* `content` for each column is GENERATED, in #col-labels -- one rule per
       label columnLabels() returns, so the count follows config rather than
       being written down as four here. Only the shared styling lives in the
       stylesheet. */
    .board td:not(.rowhead) .cell { flex: 1 1 auto; min-height: 0; padding: 8px 12px 8px 0; }

    /* A gap is the one cell you tap, so it gets the whole line. */
    .board td.is-gap .cell { width: 100%; }

    /* 44px, which is spec section 5's number and was NOT met by the card
       layout alone: a gap button came out 29px, a schedule select 22px and
       its dismiss button 17px. These are inherited sizes, but they stopped
       being inherited PROBLEMS the moment this work put each control alone
       on a line of a phone card -- so they are this work's to pay for.
       Written at the specificity of the `.cell { min-height: 0 }` rule
       above, which otherwise wins and leaves every one of them short. */
    .board td:not(.rowhead) .cell select,
    .board td:not(.rowhead) button.cell,
    .board td:not(.rowhead) .cell button { min-height: 44px; }
    /* The dismiss "x" is the one control narrow enough to miss the target on
       the other axis too. */
    .board td:not(.rowhead) .cell button.drop { min-width: 44px; }
    /* Schedule's per-cell selects are the authoring control; full width or
       they truncate the therapist's name to nothing. */
    .board td .cell select { max-width: none; width: 100%; }

    /* The bar is sticky, so every row it wraps onto is a row the reader
       never gets back -- and it stays sticky, deliberately: the gap count is
       the figure this round of work exists to make glanceable, and a header
       that scrolls away is glanceable only at the top of the page. So it
       earns the height instead. TODAY and CLINIC label eight buttons that
       already name themselves, and cost a whole row to do it. */
    .bar { gap: 6px; padding: 8px 12px; }
    .grouplabel { display: none; }
    /* And the brand goes with them, for the same reason and by measurement:
       the labels alone left the bar at 203px against a 170 budget, because
       the plan's arithmetic counted the day frame, two button rows and the
       stats -- and not the row spent saying "Scheduling board" above them.
       On a phone the page IS the board, the tab title already says so, and
       the banner above it says what the data is. 203 -> 167. */
    .brand { display: none; }
    .navgroup { gap: 0; }
    .views button { padding: 6px 10px; font-size: 12px; }
    .summary { gap: 12px; margin-left: 0; }
    .pane, .worklist { padding-left: 12px; padding-right: 12px; }
    .abs-cols { padding: 12px; gap: 12px; }
    .abs-intro, .handoff { margin-left: 0; margin-right: 0; }
    .abs-intro { padding: 12px 12px 0; }

    /* A three-column row at 88px + 1fr + auto squeezes the client name to
       nothing; the time and the cost belong on their own line. */
    .wl-item { grid-template-columns: 1fr auto; gap: 6px 10px; }
    .wl-time { grid-column: 1 / -1; }

    .scrim { padding: 10px; }
    .modal .setgrid { grid-template-columns: 1fr; }
  }

  @media (prefers-reduced-motion: no-preference) {
    .scrim { animation: fade .12s ease-out; }
    .modal { animation: rise .16s ease-out; }
    @keyframes fade { from { opacity: 0 } }
    @keyframes rise { from { opacity: 0; transform: translateY(6px) } }
  }

/* ---------------------------------------------------------------------------
 * Read-only: three controls became links.
 *
 * Everything above this line is the prototype's stylesheet unchanged, so it
 * can be diffed against `docs/prototypes/board/index.html` and shown to be a
 * port rather than a redesign. This block is the only addition, and it exists
 * because the transliteration changed ELEMENTS, not appearance:
 *
 *   the view switcher   <button onclick="setView(…)">  ->  <a href="/board/…">
 *   the weekday picker  <select onchange="…">          ->  two <a> pager chips
 *   "Start filling →"   <button onclick="setView(…)">  ->  <a href="/board/…">
 *
 * A read-only board navigates where the prototype mutated state, and a URL is
 * a better thing to be able to send someone than a click. But the rules above
 * are written against `button`, so without these the reviewed segmented
 * control renders as four underlined words run together -- which is a
 * difference in the port, not a difference the clinic agreed to.
 *
 * Appended rather than edited in place for that reason: the reviewed
 * stylesheet stays byte-identical to its source and the delta is one readable
 * block, instead of a dozen selectors quietly widened where nobody would see
 * that they had been.
 * ------------------------------------------------------------------------ */
.views a {
  font: inherit; font-size: 12.5px; padding: 7px 12px;
  border: 0; border-right: 1px solid var(--line);
  background: var(--surface); color: var(--ink);
  text-decoration: none; cursor: pointer; white-space: nowrap;
}
.views a:hover { background: var(--surface-2); }
.views a:last-child { border-right: 0; }
.views a[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
.views a:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.views a.step { font-weight: 600; }

/* The pager. `.chip` already styles the box; an anchor brings an underline
   and a text baseline a button does not, so both are undone here. */
a.chip { text-decoration: none; display: inline-flex; align-items: center; }

/* `.btn` is class-based and already matches, so this is the underline alone. */
a.btn { text-decoration: none; display: inline-block; }

@media (max-width: 900px) {
  .views a { padding: 6px 10px; font-size: 12px; }
}

/* ---------------------------------------------------------------------------
   Sign-in (plan 3). The prototype has no such screen, so nothing here is
   ported and nothing above this line changes.
   --------------------------------------------------------------------------- */
.signin {
  max-width: 22rem;
  margin: 4rem auto;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.signin h1 { font-size: 1.1rem; margin: 0 0 0.5rem; }
.signin input[type="email"],
.signin input[name="code"],
.signin input[name="name"] {
  font: inherit;
  padding: 0.5rem 0.6rem;
  border: 1px solid #bcc4d0;
  border-radius: 4px;
}
.signin input[name="code"] { letter-spacing: 0.4em; text-align: center; }
.signin .hint { font-size: 0.85rem; color: #5a6472; margin: 0; }
/* `padding: 0` is load-bearing: the page-wide `.refusal` sets `0 18px`, which
   this rule does not override by setting only the colour -- so every refusal
   in a sign-in form sat 18px in from the form it belongs to. */
.signin .refusal { font-size: 0.9rem; color: #a12d2d; margin: 0; padding: 0; }
/* Plan 7: a page may hold more than one form inside `.signin` -- a passkey's
   above a code's, or an enrolment's above a way out. Each lays out as the
   single form did. */
.signin form { display: flex; flex-direction: column; gap: 0.75rem; }
.signin form + form { border-top: 1px solid var(--line); padding-top: 1rem; }
.signin button {
  font: inherit;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.signin button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.refusal-page { max-width: 32rem; margin: 4rem auto; }
.sessions { max-width: 56rem; margin: 2rem auto; }
.sessions table { border-collapse: collapse; width: 100%; }
.sessions th, .sessions td { text-align: left; padding: 0.4rem 0.6rem; border-bottom: 1px solid #e3e7ed; }
.sessions .tag { font-size: 0.75rem; color: #5a6472; }
.sessions .display-here { margin-top: 2rem; }
.bar .signout { display: inline; }

/* ---------------------------------------------------------------------------
   The first controls (plan 4a). Everything above the plan-3 block is still
   byte-identical to the ported stylesheet.
   --------------------------------------------------------------------------- */
/* 18px to match `.abs-intro` and `.abs-cols`; without it this is the one
   block on the page flush to the window edge. */
.mark-out { display: flex; gap: 0.5rem; align-items: center; margin: 0 0 1rem;
            padding: 0 18px; }
.mark-out select { font: inherit; padding: 0.35rem 0.5rem; }
.mark-in { display: inline; }
.mark-in button { font-size: 0.75rem; padding: 2px 6px; }
.refusal { color: #a12d2d; margin: 0.5rem 0; padding: 0 18px; }

.abs-chip-form { display: inline; }
/* `.abs-row` is `justify-content: space-between` over six items (name,
   out-text, three toggle forms, Undo), and `out_text` is the one whose
   width changes on a toggle -- "out" today, "out AM+MidPM" after a press.
   A fixed reservation sized to the longest reading fixes the drift but
   pays for the rare long case on every row, including the common "out"
   one, in a 427px two-up panel that has no width to spare (measured
   against the live board: a reservation wide enough for the joined form
   left as little as 25px for the name column, wrapping names and the
   widest chip onto a second line -- worse than the drift it fixed).

   `margin-left: auto` on the first toggle form instead: it claims all the
   row's free space for itself, so the toggle forms and Undo -- a constant
   width, always all three chips plus Undo regardless of what's out --
   land flush against the row's right edge. Their own combined width never
   changes when `out_text` does, so their position doesn't either, with no
   width reserved on a row that has none to give. `:first-of-type` matches
   the FIRST `<form>` among `.abs-row`'s children specifically because
   `.mark-in` is a `<form>` too -- an unscoped `.abs-chip-form { margin-
   left: auto }` would split the row's free space across all three chip
   forms and open gaps between them instead of pushing the group as one.

   This holds as long as the row has free space left to claim, true for
   "out" and for the longest reading two of three sessions can produce
   ("out MidPM+Late PM"; the third case, all sessions individually toggled,
   collapses back to "out" -- see `_out_text`) against the names on the
   live board. A row that still overflows falls back to the browser's
   ordinary flex-shrink behaviour, same as before any of this. */
.abs-chip-form:first-of-type { margin-left: auto; }
.abs-chip { font: inherit; font-size: 0.7rem; padding: 1px 6px; margin-right: 3px;
            border: 1px solid #ccc; background: #fff; border-radius: 3px; cursor: pointer; }
.abs-chip.is-out { background: #f3d9d9; border-color: #d9a9a9; }

/* ---------- labels ---------- */
/* The screen that tells two children apart (`M12`). Not ported from the
 * prototype -- the prototype never had it, which is the whole reason the
 * collision banner sat above every board view with nothing to click.
 *
 * NO `max-height` AND NO `overflow-y` on the list. It can run to fifteen
 * rows plus every label already set, and a fixed-height box inside the page
 * scroll is the nested scroller this board does not use anywhere: the inner
 * box swallows the gesture on a touchscreen and the page will not move. The
 * list grows and the page scrolls, the same choice `.scrim` records for the
 * modal overlay. */
.labels { max-width: 620px; margin: 0 auto; padding: 18px 20px 40px; }
.labels h1 { font-size: 1.1rem; margin: 0 0 0.4rem; }
.labels h2 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em;
             color: var(--muted); margin: 22px 0 6px; font-weight: 600; }
.labels > .hint { font-size: 0.85rem; color: var(--ink-2); margin: 0 0 14px; }
.labels .refusal { font-size: 0.9rem; color: #a12d2d; margin: 0 0 12px;
                   padding: 8px 10px; background: #fbf0f0; border: 1px solid #e3c4c4;
                   border-radius: 4px; }
.labels .reveal { margin: 0 0 6px; font-size: 0.85rem; }
.labels .reveal a { color: var(--accent); }
.labels .reveal .hint { margin-left: 8px; color: var(--muted); }

.labellist { width: 100%; border-collapse: collapse; }
.labellist td { padding: 6px 8px; border-bottom: 1px solid var(--line-soft);
                vertical-align: middle; }
.labellist td.cur { font-weight: 600; white-space: nowrap; width: 1%; }
/* The mask is dots, so it is monospaced -- a proportional font makes two
 * masked names of the same length look like different lengths, which reads
 * as information about a name nobody has asked to see. */
.labellist td.who { color: var(--ink-2); font-size: 0.9rem; }
.labellist .masked { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
                     color: var(--muted); letter-spacing: 0.05em; }
.labellist td.set { width: 1%; }
.labellist td.set input { font: inherit; font-size: 0.9rem; width: 8em;
                          padding: 4px 6px; border: 1px solid var(--line);
                          border-radius: 3px; }

.labels .actions { display: flex; align-items: center; gap: 14px; margin-top: 20px; }
.labels .actions button { font: inherit; font-size: 0.9rem; padding: 6px 14px;
                          border: 1px solid var(--accent); background: var(--accent);
                          color: var(--accent-ink); border-radius: 4px; cursor: pointer; }
.labels .actions .back { color: var(--accent); font-size: 0.9rem; }

/* Visible to a screen reader, not to the page. Each input needs a label
 * element -- a placeholder is a hint, not a name -- and the column heading
 * that would otherwise supply it is not one this table renders. */
.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; }
.labelgroup { margin: 0 0 16px; padding: 0 0 4px; }
.labelgroup h3 { font-size: 0.95rem; margin: 0 0 2px; font-weight: 600;
                 padding-bottom: 3px; border-bottom: 2px solid var(--line); }
.labelgroup h3 .held { font-weight: 400; font-size: 0.8rem; color: var(--muted);
                       margin-left: 6px; }
.labellist td.who { display: flex; flex-direction: column; gap: 1px; }
.labellist .nm { font-size: 0.9rem; color: var(--ink); }
.labellist .week { font-size: 0.78rem; color: var(--muted); }

/* ---------- roster (plan 5b) ----------
 * The label page's measure and voice, and the same rule it records: no inner
 * scroll box. A refusal can list a hundred problems and a preview a hundred
 * changes; the lists grow and the page scrolls as one. The tally table is
 * four numbers wide and fits a phone. */
.roster { max-width: 720px; margin: 0 auto; padding: 18px 20px 40px; }
.roster h1 { font-size: 1.1rem; margin: 0 0 0.4rem; }
.roster h2 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em;
             color: var(--muted); margin: 22px 0 6px; font-weight: 600; }
.roster .hint { font-size: 0.85rem; color: var(--ink-2); margin: 0 0 10px; }
.roster .refusal { font-size: 0.9rem; color: #a12d2d; margin: 0 0 12px;
                   padding: 8px 10px; background: #fbf0f0; border: 1px solid #e3c4c4;
                   border-radius: 4px; }
.roster .btn { display: inline-block; font-size: 0.9rem; padding: 6px 14px;
               border: 1px solid var(--accent); background: var(--accent);
               color: var(--accent-ink); border-radius: 4px; text-decoration: none; }
.roster .tally { border-collapse: collapse; font-size: 0.9rem; margin: 6px 0 4px; }
.roster .tally th, .roster .tally td { padding: 4px 10px; text-align: right;
                                       border-bottom: 1px solid var(--line-soft); }
.roster .tally tbody th { text-align: left; font-weight: 600; }
.roster .findings { margin: 0; padding-left: 18px; font-size: 0.9rem; }
.roster .findings li { margin: 0 0 4px; overflow-wrap: anywhere; }
.roster .where { font-weight: 600; margin-right: 4px; }
.roster .problems .where { color: #a12d2d; }
.roster .vocabulary { font-size: 0.9rem; margin: 0; }
.roster .vocabulary dt { font-weight: 600; margin-top: 6px; }
.roster .vocabulary dd { margin: 0 0 0 12px; overflow-wrap: anywhere; }
.roster .changes { list-style: none; margin: 0; padding: 0; font-size: 0.9rem; }
.roster .changes > li { padding: 6px 0; border-bottom: 1px solid var(--line-soft); }
.roster .changes .who { font-weight: 600; }
.roster .changes .where { font-weight: 400; color: var(--muted); font-size: 0.8rem;
                          margin-left: 6px; }
.roster .changes ul { margin: 2px 0 0; padding-left: 18px; overflow-wrap: anywhere; }
.roster .actions { display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
                   margin-top: 20px; }
.roster .actions form { margin: 0; }
.roster .actions button { font: inherit; font-size: 0.9rem; padding: 6px 14px;
                          border: 1px solid var(--accent); background: var(--accent);
                          color: var(--accent-ink); border-radius: 4px; cursor: pointer; }
.roster .actions button.quiet { background: transparent; color: var(--accent); }
.roster .actions .back { color: var(--accent); font-size: 0.9rem; }
.roster input[type="file"] { font: inherit; font-size: 0.9rem; max-width: 100%; }
.roster details.group { margin: 0 0 6px; }
.roster details.group > summary { cursor: pointer; font-size: 0.9rem; padding: 4px 0;
                                  overflow-wrap: anywhere; }
.roster details.group > summary .count { color: var(--muted); margin-left: 4px; }
.roster details.group > ul { margin: 2px 0 8px; }
/* Five columns on a phone: measured at 390px, the tally was 420px wide with
 * the Removed column cut off (browser pass). Tighter cells fit it. */
@media (max-width: 480px) {
  .roster { padding: 14px 14px 32px; }
  .roster .tally { font-size: 0.8rem; }
  .roster .tally th, .roster .tally td { padding: 4px 5px; }
}

/* ---------- people (plan 6a) ----------
 * Forms, in the same card the roster page uses. A label sits ABOVE its
 * control rather than beside it: the browser pass found the default inline
 * flow put "First name" against its box and squeezed the multi-selects to a
 * few characters wide.
 *
 * Both "Add" buttons sit together at the top. The pass measured "Add a staff
 * member" 2,113px down a 3,678px page, below ninety children -- the same
 * shape of defect as the Import button 24,000px down the preview in 5b. */
.people-actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
                  margin: 0 0 14px; }
.people-search { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
                 margin: 0 0 16px; }
.people-search label { font-size: 0.8rem; color: var(--muted); }
.people-search input { font: inherit; font-size: 0.9rem; padding: 5px 8px; flex-grow: 1;
                       min-width: 160px; background: var(--surface); color: var(--ink);
                       border: 1px solid var(--line); border-radius: var(--radius); }
.people-search button { font: inherit; font-size: 0.9rem; padding: 5px 12px; cursor: pointer;
                        background: var(--surface); color: var(--ink);
                        border: 1px solid var(--line); border-radius: var(--radius); }
.people-list { list-style: none; margin: 0; padding: 0; font-size: 0.9rem; }
.people-list > li { display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline;
                    padding: 6px 0; border-bottom: 1px solid var(--line-soft); }
.people-list .hint { margin: 0; font-size: 0.8rem; }
.people-list .caution { color: var(--caution); }
.roster .count { color: var(--muted); font-weight: 400; margin-left: 4px; }

.person fieldset { border: 1px solid var(--line); border-radius: var(--radius);
                   padding: 14px 16px 4px; margin: 0 0 16px; }
.person legend { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em;
                 color: var(--muted); padding: 0 6px; }
.person label { display: block; font-size: 0.8rem; color: var(--muted); margin: 0 0 4px; }
.person input[type="text"], .person input[type="email"], .person input[type="date"],
.person select { font: inherit; font-size: 0.9rem; width: 100%; max-width: 420px;
                 padding: 6px 8px; color: var(--ink); background: var(--surface);
                 border: 1px solid var(--line); border-radius: var(--radius); }
/* Checkbox lists rather than `select multiple`: a 72-option box is a 148px
 * window scrolling 1300px, which is a vertical scroller inside the page's
 * own and breaks touch (browser pass, and the standing rule about nested
 * scrollviews). These grow the page instead. */
.person .picklist { list-style: none; margin: 0; padding: 0;
                    columns: 2; column-gap: 20px; }
.person .picklist li { break-inside: avoid; }
.person .picklist label { display: flex; align-items: center; gap: 7px;
                          font-size: 0.9rem; color: var(--ink); margin: 0 0 3px; }
.person .picklist input { width: auto; }
.person details.picker { border: 1px solid var(--line); border-radius: var(--radius);
                         padding: 6px 10px; max-width: 420px; }
.person details.picker > summary { cursor: pointer; font-size: 0.8rem; color: var(--muted); }
.person details.picker > summary .count { color: var(--ink-2); margin-left: 4px; }
.person details.picker > .picklist { margin-top: 8px; }
@media (max-width: 480px) { .person .picklist { columns: 1; } }
.person input:focus-visible, .person select:focus-visible { outline: 2px solid var(--accent);
                                                            outline-offset: 1px; }
/* A checkbox keeps its label beside it: a tick with its name above it reads
 * as a heading over an unlabelled box. */
.person label:has(input[type="checkbox"]) { display: flex; align-items: center; gap: 8px;
                                            font-size: 0.9rem; color: var(--ink); }
.person label:has(input[type="checkbox"]) input { width: auto; }
.person .hint { display: block; margin: 4px 0 0; font-size: 0.8rem; }
.person button[type="submit"] { font: inherit; font-size: 0.9rem; padding: 6px 14px;
                                border: 1px solid var(--accent); background: var(--accent);
                                color: var(--accent-ink); border-radius: 4px; cursor: pointer; }
.person .field-findings { list-style: none; margin: 4px 0 0; padding: 0; font-size: 0.85rem;
                          color: #a12d2d; }
.person-warnings { border: 1px solid var(--line); border-left: 3px solid var(--advisory);
                   background: var(--advisory-soft); border-radius: var(--radius);
                   padding: 10px 14px; margin: 0 0 16px; }
.person-warnings h2 { margin-top: 0; }
.person-warnings ul { margin: 0; padding-left: 18px; font-size: 0.9rem; }
.person-warnings > ul > li { margin: 0 0 6px; }
.person-warnings .whose { font-weight: 600; margin-right: 4px; }

@media (max-width: 480px) {
  .person input[type="text"], .person input[type="email"], .person input[type="date"],
  .person select { max-width: 100%; }
}

/* ---------- verify ---------- */
/* The table verification prototype, in row strips (2026-09-15 spec, RS-1):
 * each table card is a header line of printed column headers, then, per
 * data row, that row's strip of the straightened scan with the model's
 * reading on its own line directly under it -- one live control per cell --
 * and the printed row labels as text in a column pinned on the left.
 * Nothing here scrolls VERTICALLY on its own -- no max-height, no
 * overflow-y -- for the reason `.labels` records: the page scrolls as one,
 * and vertical inside vertical is hard to use on a touchscreen (the
 * engineer, 2026-09-14). A table wider than its card scrolls sideways
 * inside `.sscroll` instead, its labels pinned -- that axis is allowed to
 * nest; only vertical-in-vertical is not. EVERY VALUE LOOKS THE SAME (RS-3):
 * one tint for the reading line, no class for a planted cell or a model's
 * confidence. The two marks the spec makes are an unread cell's dashed
 * outline (`:invalid` on a required control with nothing in it) and a
 * re-check row's tint (`.sread.recheck`), the reviewer's own disagreement
 * with a hidden count (RS-5). */
.verify { max-width: 960px; margin: 0 auto; padding: 12px 16px 48px; }
.verify h1 { font-size: 1.05rem; margin: 6px 0 4px; }
.verify .vtop { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
.verify .vtop .back { color: var(--accent); }
.verify .progress { color: var(--muted); font-size: 0.85rem; }
.verify > .hint, .verify #page-status { font-size: 0.9rem; color: var(--ink-2); margin: 0 0 12px; }
/* Approve page sits in the status line once every table is saved, and again at
 * the foot (`#page-approve`), above "Can't verify this page", where the thumb
 * is after the last table: a page is verified by it, not by its last save
 * (the engineer, 2026-09-13). */
.verify .vapprove { margin: 8px 0 0; }
.verify #page-approve .vapprove { margin: 18px 0 0; }
.verify > .refusal { margin: 0 0 8px; padding: 0; font-size: 0.9rem; }
.verify .vqueue { list-style: none; padding: 0; margin: 0; }
.verify .vqueue li a { display: grid; grid-template-columns: 2rem 1fr 1fr auto; gap: 8px;
                       padding: 10px 8px; border-bottom: 1px solid var(--line); color: inherit;
                       text-decoration: none; }
.verify .vqueue .vq-date { color: var(--muted); }
.verify .vqueue .vq-status { color: var(--muted); font-size: 0.8rem; grid-column: 2 / -1; }
.verify .vqueue .vq-verified .vq-status { color: var(--ok); }
.verify .vqueue .vq-rejected .vq-status { color: var(--gap); }
.vcard { background: var(--surface); border: 1px solid var(--line); border-radius: 6px;
         margin: 0 0 14px; padding: 10px 12px 12px; }
.vcard h2 { font-size: 0.95rem; margin: 0 0 8px; display: flex; gap: 8px; align-items: baseline; }
.vcard h2 .kicker { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.06em;
                    color: var(--muted); }
.vcard h2 .saved-tag { margin-left: auto; font-size: 0.75rem; color: var(--ok); }
/* A saved card recedes by its heading, not the whole card: an opacity on the
 * card would fade [Edit] with it, and a child cannot undo its parent's, so
 * the one control left on the card would look disabled. */
.vcard-saved { padding-bottom: 8px; }
.vcard-saved h2 { opacity: 0.7; }
/* [Edit] on a saved card, while the page is not yet approved: the secondary
 * action, outlined rather than filled, under the heading's "saved". */
.vreopen { display: flex; justify-content: flex-end; margin: 0; }
.vreopen button { font: inherit; font-size: 0.9rem; padding: 6px 14px; border-radius: 6px;
                  border: 1px solid var(--accent); background: transparent; color: var(--accent); }
.vcard form { margin: 0; }
/* The strips' own scroller: horizontal only, never a height or overflow-y.
 * The grid inside is as wide as its content (`max-content`), so the sticky
 * label column stays pinned across the whole scroll rather than only across
 * the scroller's width; a table narrower than the card just sits at its
 * width. */
.sscroll { overflow-x: auto; overflow-y: hidden; border: 1px solid var(--line-soft);
           background: var(--surface); }
.sgrid { display: grid; width: max-content; }
.scorner, .slabel { position: sticky; left: 0; z-index: 2; background: var(--surface);
                    box-shadow: 3px 0 4px -3px rgba(0,0,0,.35); }
.scorner { font-size: 0.75rem; color: var(--muted); padding: 4px 6px 2px; }
/* The header line: each printed column header centred over its column, at
 * the same left and width the layout gives the column's control. */
.shead { position: relative; height: 1.7rem; }
.shead span { position: absolute; bottom: 3px; text-align: center; font-size: 0.8rem;
              font-weight: 600; color: var(--ink-2); white-space: nowrap; overflow: hidden;
              text-overflow: ellipsis; }
.slabel { font-size: 0.8rem; line-height: 1.25; color: var(--ink); padding: 6px 8px 6px 6px;
          border-top: 1px solid var(--line); display: flex; align-items: center;
          overflow-wrap: anywhere; }
.spair { border-top: 1px solid var(--line); padding: 5px 0 7px; }
/* One straightened image per table (RS-2), `--strip` on the grid; each
 * row's strip is a slice of it -- the image at the grid's width, shifted up
 * to the row's band by the inline `background-position` -- so a page costs
 * one image request per table. A tap opens the raw crop (RS-1): the strip
 * is a link. */
.sstrip { display: block; width: 100%; background-image: var(--strip);
          background-size: 100% auto; background-repeat: no-repeat; background-color: #fff; }
/* A notes card's one strip: the whole region at the card's width, its height
 * from the layout's aspect ratio (RS-6). */
.snotes { border: 1px solid var(--line-soft); margin: 0 0 8px; }
/* The reading line: one tint for every value; each value is its control,
 * at least 36px tall and as wide as its column (RS-4). */
.sread { position: relative; height: 42px; margin-top: 3px; background: var(--accent-soft); }
.sread.recheck { background: var(--caution-soft); }
.rv { position: absolute; top: 3px; height: 36px; margin: 0; padding: 0 2px;
      box-sizing: border-box; -webkit-appearance: none; appearance: none; box-shadow: none;
      /* 16px, so a phone does not zoom in on a tapped box. */
      font: 600 16px/1 var(--ui); color: var(--ink); background: var(--surface);
      border: 1px solid var(--line); border-radius: 5px;
      text-align: center; text-align-last: center; }
select.rv { cursor: pointer; }
input.rv { font-weight: 500; letter-spacing: -0.02em; }
.rv:focus { outline: 2px solid var(--accent); outline-offset: 0; }
/* An unread cell (RS-3): required with nothing in it, so `:invalid` -- the
 * dashed outline is live and needs no class. A count box not yet typed in
 * reads the same way. */
.rv:invalid { border-style: dashed; border-color: var(--ink-2); }
.vnotes { display: block; }
.vnotes textarea { font: inherit; padding: 4px 6px; border: 1px solid #bcc4d0;
                   border-radius: 4px; width: 100%; box-sizing: border-box; }
.vcard .actions { display: flex; gap: 14px; align-items: center; margin-top: 10px; flex-wrap: wrap; }
.vcard .actions button, .vreject button, .vapprove button {
  font: inherit; font-size: 0.95rem; padding: 8px 16px; border-radius: 6px;
  border: 1px solid var(--accent); background: var(--accent); color: var(--accent-ink); }
.vcard .actions .hint { font-size: 0.85rem; color: var(--ink-2); }
.vcard .refusal { margin: 0 0 8px; padding: 6px 8px; }
.recheck-note { color: var(--caution); margin: 0 0 6px; font-size: 0.9rem; }
/* A `<details>`, not a lone button: the tap that raises the question is not
 * the tap that answers it, since a rejection takes a whole page out of both
 * of the report's headline numbers for good. `<summary>` reads as the old
 * single danger button; opening it stacks the confirm form underneath, never
 * beside, and nothing here scrolls or widens the page on its own. */
.vreject { margin-top: 18px; }
.vreject summary { display: inline-block; cursor: pointer; list-style: none; font: inherit;
                   font-size: 0.95rem; padding: 8px 16px; border-radius: 6px;
                   border: 1px solid var(--gap-line); background: #fff; color: var(--gap); }
.vreject summary::-webkit-details-marker { display: none; }
.vreject form { display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
                margin-top: 12px; }
.vreject button.danger { background: #fff; color: var(--gap); border-color: var(--gap-line); }
@media (max-width: 480px) {
  .verify { padding: 10px 8px 40px; }
}

/* ---------- a child's week (plan 6b) ---------- */
/* One fieldset per weekday, four cells inside it, and the page grows
   downwards. NO SCROLL REGION: twenty cells is a long page and a long page is
   fine, while a scrollview inside the page's own swallows the gesture on a
   touchscreen and the page will not move. The team picker in 6a made the same
   call for the same reason. */
.week fieldset            { margin: 0 0 1rem; }
.week .cell               { display: flex; flex-wrap: wrap; align-items: baseline;
                            gap: 0.5rem; margin: 0 0 0.5rem; }
.week .cell label         { flex: 0 0 8.5rem; color: var(--muted); }
.week .cell select        { flex: 1 1 14rem; max-width: 26rem; }
.week .cell .field-findings { flex: 1 0 100%; margin: 0.15rem 0 0 8.5rem; }
.copy-week                { border: 1px solid var(--line); border-radius: var(--radius);
                            padding: 0.75rem; margin: 0 0 1rem;
                            background: var(--surface); }
.copy-week label          { display: block; margin-bottom: 0.35rem; color: var(--muted); }
.copy-week select         { max-width: 18rem; }
.copy-week .hint          { display: block; margin-top: 0.4rem; }

@media (max-width: 480px) {
  /* The label stops holding a column of its own, so a cell is two full-width
     rows rather than a squeezed pair. */
  .week .cell label            { flex: 1 0 100%; }
  .week .cell .field-findings  { margin-left: 0; }
}

/* ---------- the clinic's week (plan 6b) ---------- */
/* NO INNER SCROLLER, for the reason `.scroll` on the day board gives and one
   more this page found on screen.
 *
 * The rule first: a vertical scrollview inside the page's own swallows the
 * gesture on a touchscreen and the page will not move. The wrapper is
 * therefore `overflow: visible` and the PAGE scrolls sideways, with the table
 * at `width: max-content`.
 *
 * The one this page found: `overflow-x: auto` makes a box the SCROLL
 * CONTAINER on both axes for sticky purposes -- CSS computes `overflow-y:
 * auto` when either axis is not visible -- so a `position: sticky` header
 * inside it measures `top` from the BOX rather than the viewport. With
 * `top: 71px` the header rendered 71px down, on top of rows two and three,
 * on first load and without scrolling. Only a browser pass shows that; every
 * test passed. A horizontal scroller and a viewport-sticky header cannot
 * coexist, which is exactly why the day board made this choice already.
 *
 * `top: 0` rather than `var(--bar-h)`: this page carries no header band, the
 * same as the other roster pages, so there is no bar to clear. */
.roster.wide             { max-width: none; }
.cweek-wrap              { overflow: visible; }
table.cweek              { border-collapse: separate; border-spacing: 0;
                           width: max-content; font-size: 0.85rem; }
table.cweek th,
table.cweek td           { border: 1px solid var(--line-soft); padding: 3px 7px;
                           text-align: left; white-space: nowrap; }
table.cweek thead th     { position: sticky; top: 0; z-index: 2;
                           background: var(--surface); font-weight: 600; }
table.cweek th.slabel    { position: sticky; left: 0; z-index: 1; background: var(--surface);
                           box-shadow: 3px 0 4px -3px rgba(0,0,0,.35); }
table.cweek thead th.slabel { z-index: 3; }
table.cweek th.has-open .count { display: block; font-weight: 400; color: var(--muted);
                           font-size: 0.75rem; }
table.cweek td.is-open   { color: var(--muted); font-style: italic; }
table.cweek td.is-flagged { background: var(--advisory-soft);
                           box-shadow: inset 2px 0 0 var(--advisory); }

/* Which cell a week warning is about. The panel sits above twenty selects,
   so "with Bree Baker" on its own names a person and no place. */
.person-warnings .which { font-variant-numeric: tabular-nums; color: var(--muted);
                          font-size: 0.8rem; margin-right: 0.35rem; }

/* ---------- lunch tables (plan 6b) ---------- */
/* ONE COMPACT ROW PER TABLE, and one weekday per page. The browser pass
   measured the alternative at 25,000px with Friday 20,000px down -- 5b's
   Import button all over again. A row is name, seats, who runs it, save. */
.lunch-admin .daypick    { display: flex; gap: 0.25rem; margin: 0 0 1rem; }
.lunch-admin .daypick a  { padding: 4px 12px; border: 1px solid var(--line);
                           border-radius: var(--radius); text-decoration: none;
                           color: var(--muted); }
.lunch-admin .daypick a.here { background: var(--accent); border-color: var(--accent);
                           color: var(--accent-ink); }
.lunch-admin h2          { margin: 0 0 0.5rem; }
.lunch-admin .table-list { list-style: none; padding: 0; margin: 0 0 1rem; }
.lunch-admin .table-list > li { display: flex; align-items: center; flex-wrap: wrap;
                           gap: 0.5rem; padding: 4px 0;
                           border-bottom: 1px solid var(--line-soft); }
.lunch-admin .table-name { flex: 0 0 8rem; font-weight: 600; }
.lunch-admin .table-list .hint { flex: 0 0 9rem; }
.lunch-admin .table-list form { display: contents; }
.lunch-admin .table-list select { max-width: 14rem; }
.lunch-admin .add-table  { border: 1px solid var(--line); border-radius: var(--radius);
                           padding: 0.75rem; }
.visually-hidden         { position: absolute; width: 1px; height: 1px; overflow: hidden;
                           clip: rect(0 0 0 0); white-space: nowrap; }

@media (max-width: 480px) {
  .lunch-admin .table-name,
  .lunch-admin .table-list .hint { flex: 1 0 100%; }
}

/* ---------------------------------------------------------------------------
   Passkeys (plan 7), under Setup. One row per passkey; it wraps rather than
   scrolls at phone width, and the only-passkey warning takes its own line.
   --------------------------------------------------------------------------- */
.passkeys .passkey-list { list-style: none; padding: 0; margin: 0 0 12px; }
.passkeys .passkey-list li {
  display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline;
  padding: 8px 0; border-bottom: 1px solid var(--line);
}
.passkeys .passkey-list .name { font-weight: 600; }
.passkeys .passkey-list form { margin-left: auto; }
.passkeys .passkey-list .hint { margin: 0; }
.passkeys .passkey-list .hint.warn { flex-basis: 100%; color: var(--gap); }

/* The code an approver matches against (`M79`). Monospaced and spaced out
   because it is read down a phone one character at a time, and letter-spacing
   is what stops `RN` being heard as `M`. */
.approval-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.15rem;
  letter-spacing: 0.18em;
  margin: 0;
}
.signin .approval-code { font-size: 1.6rem; margin: 4px 0 12px; }
/* `.roster .btn` paints every button the accent colour, so Remove, Revoke and
   Refuse arrived looking exactly like Approve -- the destructive choice and
   the safe one, side by side and identical. Specificity beats it back. */
.passkeys .btn.danger {
  background: var(--surface); color: var(--gap); border-color: var(--gap-line);
}
/* A finger, not a mouse, at phone width: the board's own rule is 44px. */
@media (max-width: 480px) {
  .signin button, .passkeys button { min-height: 44px; }
  /* "Add a passkey" is a link dressed as a button, which the rule above does
     not reach: 35px at 390px when the pass was run again. Flex centres its
     text, which an inline-block link does not do the way a button does. */
  .passkeys a.btn { min-height: 44px; display: inline-flex; align-items: center; }
}
/* A colleague's browser waiting for approval (`M71`): on every admin page,
   beneath the synthetic banner when there is one. */
.approval-banner {
  background: var(--accent-soft); color: var(--ink); border-bottom: 1px solid var(--line);
  padding: 8px 18px; font-size: 0.9rem;
}
