/* Teacher attendance sheet (task 3072).
 *
 * This is the one page in the app that is used OUTDOORS, one-handed, by someone
 * who is counting children onto a bus that is about to leave. Everything here
 * follows from that:
 *
 *  - Light, near-paper surface. The rest of the app is dark because it is read
 *    indoors; a dark sheet in direct Florida sun is the hardest thing to read on
 *    a phone. The header keeps the brand navy so the page still belongs.
 *  - Nothing smaller than 44px is tappable, and nothing depends on :hover -
 *    there is no hover on a phone.
 *  - State is never carried by colour alone (a filled check + a word as well),
 *    and the running count sits in a sticky bar so it is legible without
 *    scrolling, at arm's length.
 *
 * DENSITY (task 3106) - Eli's first real-use feedback, from a phone during an
 * actual dismissal: "the boxes are so big that the teacher has to scroll down
 * to see the full roster." He is right, and the number that matters is not
 * padding, it is HOW MANY CHILDREN ARE ON SCREEN AT ONCE. On a 375x667 phone
 * that was 6 of 34. It is now 11.
 *
 * Where the space came from, in this order - everything that is NOT a tap
 * target first:
 *   1. The count bar collapsed from two stacked lines to one (69px -> 42px).
 *   2. The filter chips came down from 54px to 46px and lost 3px of padding.
 *   3. The header lost 6px of padding (it still holds 44px controls).
 *   4. Rows last: 68px -> 50px, by removing air, not by removing a tap target.
 *
 * THE FLOOR IS 44px AND IT IS NOT NEGOTIABLE (WCAG 2.5.5, and the outdoor
 * reality: a teacher mis-tapping while children board is a worse failure than
 * scrolling). Every tappable thing here sits at 46px or more, deliberately
 * above the line rather than on it, because a row is tapped one-handed at
 * arm's length and the two rows either side of it are also tap targets.
 * `test/attendance-ui.e2e.js` asserts the whole band (44-56px);
 * `test/mutations/attendance-3106.js` proves the assertion bites from both
 * sides.
 *
 * DENSITY ROUND 2 (task 3109) - Danny, after seeing the above: "the names are
 * way too big still. Maybe we could make them smaller and fit two columns on
 * one screen." Two changes and one measured refusal:
 *
 *   NAME 17px -> 15px, ROW 50px -> 46px. The row is driven by the BOARDED
 *   height, not the name: 3106 picked 50 because 6 + 20.4 + 1 + 16.3 + 6 is
 *   the height a boarded row needs for its "Aboard 3:52 - Mrs Quint" second
 *   line, and a row that grows the moment it is tapped shifts the list under
 *   the teacher's thumb mid-run. At a 15px name the same arithmetic lands on
 *   45.25, so 46 is the SAME decision re-derived, not a weakening of it. On a
 *   375x667 phone: 10 rows on opening -> 11, 11 while scanning -> 12.
 *
 *   TWO COLUMNS ONLY AT >=600px - SEE .srows BELOW. Measured against the real
 *   10/15 roster, not assumed. A 375px phone gives each column a 122px name
 *   box; the longest real name renders 190px wide at 15px, so you would need a
 *   ~9.6px name for it to fit on one line, and at a readable size 245 of 313
 *   real names break mid-word. Worse, a boarded row in that layout grows from
 *   46px to 58-76px - every one of Blue 3:45's 34 rows - which is exactly the
 *   shift-under-the-thumb failure 3106's row height exists to prevent.
 *
 * Nothing on this page ever TRUNCATES a name (`overflow-wrap: anywhere`, no
 * ellipsis anywhere near .nm). That is deliberate and it is what makes the
 * two-column question a readability question rather than an identity one: a
 * column too narrow costs a wrapped name, never an ambiguous one. Keep it.
 */

:root {
  --ink: #0b1220;
  --ink-2: #334155;
  --muted: #5b6b85;
  --line: #d3dbe6;
  --bg: #eef2f7;
  --card: #ffffff;
  --brand: #00263e;
  --amber: #b45309;
  --amber-bg: #fff7ed;
  --amber-line: #fdba74;
  --amber-solid: #f59e0b;
  --green: #15803d;
  --green-bg: #e7f8ec;
  --green-line: #86c79b;
  --red: #b3261e;
  --red-bg: #fdecea;
}

/* ---- surface overrides (admin.css paints a dark dashboard) ---- */
body.att-body {
  background: var(--bg);
  color: var(--ink);
  -webkit-text-size-adjust: 100%;
  /* admin.css paints `html` dark, and a background on <body> only propagates
     to the canvas when <html> has none - so a SHORT attendance screen (the
     empty state, a rosterless day) showed a band of the dark dashboard below
     the light sheet. Found in the browser during task 3102, present since
     3072. `svh` for the phone, where 100vh is the toolbar-expanded height and
     would add a pointless scroll. */
  min-height: 100vh;
  min-height: 100svh;
}
body.att-body main {
  padding: 0 0 28px;
  max-width: 720px;
  margin: 0 auto;
}
body.att-body h1, body.att-body h2 { color: var(--ink); }
body.att-body .muted { color: var(--muted); }
/* Kill the 300ms double-tap-zoom delay on every control on the page: a teacher
   tapping 34 names fast must not have each tap held back by the gesture
   detector, and a stray second tap must not zoom the sheet. */
body.att-body button, body.att-body .srow, body.att-body .buscard { touch-action: manipulation; }
body.att-body button:focus-visible,
body.att-body [tabindex]:focus-visible { outline: 3px solid #1d4ed8; outline-offset: 2px; }

/* ---- header ----
   Deliberately NOT sticky: the sticky budget on a 375px screen belongs to the
   running count and the filter, not to branding. Which bus you are on lives in
   the count bar, so this row may truncate without losing anything. */
/* 5px of padding, not 8 (task 3106): the row's height is set by the 44px
   controls inside it, so the padding is pure chrome on the one screen a
   teacher sees first. The safe-area inset is kept - on a home-screen PWA that
   is the notch, not decoration. */
header.admin.att-head {
  gap: 10px; padding: 5px 12px;
  padding-top: calc(5px + env(safe-area-inset-top));
  flex-wrap: nowrap;
}
/* NEAR-BLACK ON NAVY - a pre-existing defect, found during the task 3106
   density pass and present since 3072. `header.admin .title` in admin.css sets
   no colour, so on the dark dashboard it inherits the light body text; on this
   page it inherited `--ink` (#0b1220) and sat on the brand navy #00263e at
   about 1.1:1. The one word identifying the page has been invisible on the
   picker screen the whole time. Same class as the .acct-card bug below: this
   page flips the body colour and the shared chrome does not know it. */
.att-head .title {
  font-size: 15px; font-weight: 800; min-width: 0; flex: 1 1 auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: #e2e8f0;
}
/* In a run the back arrow takes the left slot and the count bar names the bus,
   so a half-truncated brand string would be the only thing on screen that
   tells the teacher nothing. Drop it instead. */
.att-head.in-run .title { display: none; }
@media (min-width: 520px) { .att-head.in-run .title { display: block; } }
.att-head nav { flex: 0 0 auto; }
/* 44px, not the 40px this shipped with (task 3090, QA judgement call J1). The
   attendance controls below held 44 everywhere; the header did not, and it is
   the row a teacher's thumb reaches for while she is looking at the bus. Not
   gated on a pointer type here the way admin.css gates the other ten pages:
   this page has exactly one audience and it is holding a phone. */
.att-head nav a {
  font-size: 13px; display: inline-flex; align-items: center;
  min-height: 44px; padding: 0 8px;
}
.att-back {
  flex: 0 0 auto; width: 44px; height: 44px; margin: -6px 0;
  background: #0b3c5d; color: #e2e8f0; border-radius: 10px;
  font-size: 26px; line-height: 1; font-weight: 700; padding: 0;
}
/* The account menu (shell.js) is the teacher's ONLY route to a password
   change - /admin is closed to them - so it stays, at a tappable size. */
header.admin.att-head .acct { flex: 0 0 auto; gap: 6px; }
header.admin.att-head .acct-who { display: none; }
header.admin.att-head .acct-btn { min-height: 44px; min-width: 44px; padding: 0 12px; }
/* SIGN OUT IS THE EXPENSIVE MIS-TAP (task 3090 / J1). It is pixels away from
   Map and Password in a row tapped one-handed while children board, and the
   action cannot be taken back - by the time beforeunload could ask about
   unsaved taps, the session is already revoked. Three defences, in order of
   how much they do: attendance.js puts a confirm in front of it, it is moved
   off the cluster, and it stops wearing the same filled chip as its
   neighbours so the eye can tell them apart at arm's length. */
header.admin.att-head .acct-btn[data-act="out"] {
  margin-left: 12px; background: transparent;
  border: 1px solid #2f5d7c; color: #c7d8e6;
}
@media (min-width: 620px) {
  header.admin.att-head .acct-who { display: inline; }
}
/* The change-password and sign-out sheets come from admin.css, which paints
   them for the dark dashboard. On this page body colour is near-black ink for
   a sunlit sheet, and the card inherited it onto a #1e293b panel - the heading
   and the hint line were all but invisible. Scoped back to the card's own
   palette, and its buttons get the 44px the rest of this page holds. */
body.att-body .acct-card { color: #e2e8f0; }
body.att-body .acct-card .muted { color: #94a3b8; }
body.att-body .acct-card button { min-height: 44px; font-size: 15px; }

/* ---- the sticky dock (task 3102) ----
   ONE sticky element holds the not-today notice AND the count bar. Two
   siblings each at `top: 0` would sit on top of each other, and the fix of
   offsetting the second one by the first one's height means hard-coding a
   height that changes with the font size - the exact class of bug the single
   sticky wrapper was introduced to avoid. So the dock sticks and its children
   stack normally. It carries no padding or background of its own, so on a
   normal day (no notice, picker view) it is a zero-height no-op. */
.topdock { position: sticky; top: 0; z-index: 20; }

/* ---- "this is not today": the loudest thing on the page ----
   PERSISTENT AND NOT DISMISSIBLE, on purpose (task 3102). It is the one state
   where every tap on screen is being written against a day that is not the one
   happening outside, so it is deliberately not an .att-banner: banners are
   messages ABOUT something and they come and go, and this is the frame the
   whole screen sits inside. Solid amber rather than the tinted amber card, so
   it cannot be mistaken for the save/filter warnings, cannot be scrolled away
   from, and survives a minute of tapping because it lives in the sticky dock
   above the roster rather than in #banners. Near-black on #f59e0b is ~8:1,
   which is what a phone in Florida sun needs. */
.datebar {
  background: var(--amber-solid); color: #3d2004;
  border-bottom: 3px solid #b45309; padding: 8px 14px;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.datebar-txt { flex: 1 1 180px; min-width: 0; }
.datebar b { display: block; font-size: 15px; font-weight: 800; line-height: 1.25; }
.datebar span { display: block; font-size: 13px; font-weight: 700; line-height: 1.3; }
.datebar button {
  flex: 0 0 auto; min-height: 44px; padding: 0 14px; border-radius: 10px;
  background: #3d2004; color: #fff; font-size: 15px; font-weight: 800;
}

/* ---- the running count: the one thing readable without scrolling ----
   ONE LINE as of task 3106. It still has to be legible at arm's length, so the
   number stays the largest thing on the page by some way (26px/800 against a
   17px roster), but it no longer gets a line of its own with the bus/date
   context stacked above it: the two share a row, the count takes what it needs
   and the context takes the rest and truncates. 69px -> ~42px, every pixel of
   it permanently stuck to the top of a 667px screen, which is why this was
   worth doing before touching a single row. */
.stickytop { background: var(--card); }
.countbar {
  border-bottom: 1px solid #e6ebf2; padding: 4px 12px 5px;
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
}
/* Truncates rather than wraps, and the truncation order is deliberate: the
   string is "{Bus} bus - {dismissal} - {date}", so a 320px screen loses the
   tail of the date and keeps "Blue bus - 3:45" - which is the whole of the
   "am I on the right bus?" check. The date only has to be read when it is NOT
   today, and then the amber notice above carries it in full. */
.count-ctx {
  flex: 1 1 0; min-width: 0;
  font-size: 12.5px; font-weight: 700; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-align: right;
}
.count-main { flex: 0 0 auto; display: flex; align-items: baseline; gap: 6px; }
.count-num {
  font-size: 26px; font-weight: 800; letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums; color: var(--ink); line-height: 1.25;
}
.count-lab { font-size: 14px; font-weight: 700; color: var(--muted); }
/* "+1 off-roster", "2 saving", "not syncing" - a whole line of its own, but
   only on the days it has something to say. Without `:empty` an always-present
   empty block would re-spend the pixels this bar just saved. */
.count-extra { flex: 1 0 100%; font-size: 12.5px; color: var(--amber); font-weight: 700; }
.count-extra:empty { display: none; }
.count-done .count-num { color: var(--green); }

/* ---- the filter: the most important control on the page ----
   46px, down from 54 (task 3106), and NOT 44: this is a tap target that stays
   on screen for the whole run, two of them side by side, and the floor is a
   floor rather than a target. It stays in the sticky dock at full width - a
   teacher who has boarded 31 of 34 gets to the three missing children in one
   tap, and that is worth more than the 46px it costs. */
.filters {
  display: flex; gap: 8px; padding: 5px 10px 6px;
  background: var(--card); border-bottom: 1px solid var(--line);
}
.fchip {
  flex: 1 1 0; min-height: 46px; padding: 4px 10px;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: #e8edf5; color: var(--ink-2); border: 2px solid transparent;
  border-radius: 12px; font-size: 16px; font-weight: 700;
}
.fchip-n {
  font-variant-numeric: tabular-nums; font-size: 18px; font-weight: 800;
  background: rgba(255,255,255,.75); border-radius: 8px; padding: 1px 8px;
}
.fchip.wanted { background: var(--amber-bg); color: var(--amber); border-color: var(--amber-line); }
.fchip.wanted.clear { background: var(--green-bg); color: var(--green); border-color: var(--green-line); }
.fchip[aria-pressed="true"] { background: var(--brand); color: #fff; border-color: var(--brand); }
.fchip[aria-pressed="true"] .fchip-n { background: rgba(255,255,255,.2); color: #fff; }

.filter-hint {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: var(--amber-bg); border-bottom: 1px solid var(--amber-line);
  color: var(--amber); font-size: 13.5px; font-weight: 700; padding: 5px 12px;
}
.filter-hint button { background: none; color: var(--amber); text-decoration: underline; padding: 8px; font-size: 13.5px; }

/* ---- bus picker ----
   SIX buses and only four of them fitted on a 667px screen (task 3106), so the
   teacher whose bus is Yellow scrolled before she had started. All six fit now.
   The cards are still 64px tap targets - this is reclaimed air, not a smaller
   button. */
.pick-h { padding: 10px 14px 4px; }
.pick-h h1 { font-size: 19px; margin: 0; }
.pick-h p { margin: 3px 0 0; font-size: 13.5px; color: var(--muted); }
/* SINGLE-DISMISSAL ORIENTATION (task 3090 / J3), on the picker and in a run.
   Deliberately NOT one of the .att-banner colours: amber and red on this page
   mean something is wrong or unsaved, and an ordinary Friday is neither. A
   calm blue-grey panel reads as "here is what today is", which is the whole
   brief - a teacher who finds the roster odd that day will say so. */
.pick-h p.pick-one, .att-orient {
  margin-top: 6px; padding: 8px 11px; border-radius: 10px;
  background: #e8eef8; border: 1px solid #c4d3ea; color: #1e3a5f;
  font-size: 13.5px; font-weight: 700; line-height: 1.4;
}
.att-orient { display: block; margin: 8px 12px 0; }
/* ---- admin-only day chooser (task 3102) ----
   Only ever rendered for an admin, and only on the picker. A native
   <input type="date"> on purpose: on a phone it opens the OS date wheel, it is
   keyboard- and screen-reader-complete for free, and it cannot be fed a
   malformed string. Quiet by design - this is a training and preview tool, not
   part of taking attendance, so it must not compete with the bus cards. */
.daypick { margin: 12px 14px 0; padding: 10px 12px; border: 1px solid var(--line); border-radius: 12px; background: var(--card); }
.daypick h2 { font-size: 13px; margin: 0 0 2px; color: var(--ink-2); font-weight: 800; }
.daypick p { margin: 0 0 8px; font-size: 12.5px; color: var(--muted); line-height: 1.4; }
.daypick-row { display: flex; gap: 8px; flex-wrap: wrap; }
.daypick input[type="date"] {
  flex: 1 1 150px; min-width: 0; min-height: 44px; padding: 0 10px;
  font-size: 16px; font-family: inherit; color: var(--ink);
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
}
.daypick button {
  flex: 0 0 auto; min-height: 44px; padding: 0 14px; border-radius: 10px;
  font-size: 15px; font-weight: 700; background: var(--brand); color: #fff;
}
.daypick button.secondary { background: #e8edf5; color: var(--ink-2); }

.dpick { display: flex; gap: 8px; padding: 8px 12px 4px; }
.dpick button {
  flex: 1 1 0; min-height: 46px; border-radius: 12px; font-size: 16px; font-weight: 700;
  background: #e8edf5; color: var(--ink-2);
}
.dpick button[aria-pressed="true"] { background: var(--brand); color: #fff; }
.buslist { display: grid; gap: 7px; padding: 8px 12px; grid-template-columns: 1fr; }
@media (min-width: 540px) { .buslist { grid-template-columns: 1fr 1fr; } }
.buscard {
  display: grid; grid-template-columns: 14px 1fr auto; align-items: center; gap: 11px;
  width: 100%; text-align: left; min-height: 64px; padding: 8px 12px;
  background: var(--card); color: var(--ink);
  border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 1px 0 rgba(11,18,32,.05);
}
.buscard .bc-swatch { width: 14px; height: 34px; border-radius: 4px; border: 1px solid rgba(11,18,32,.25); }
.buscard > span:nth-child(2) { min-width: 0; }
.buscard .bc-name, .buscard .bc-sub, .buscard .bc-flag { display: block; }
.buscard .bc-name { font-size: 18px; font-weight: 800; line-height: 1.2; }
.buscard .bc-sub { font-size: 12.5px; color: var(--muted); margin-top: 1px; }
.buscard .bc-count {
  font-size: 21px; font-weight: 800; font-variant-numeric: tabular-nums; text-align: right;
  white-space: nowrap; line-height: 1.15;
}
.buscard .bc-count small { display: block; font-size: 11.5px; font-weight: 700; color: var(--muted); }
.buscard.done { background: var(--green-bg); border-color: var(--green-line); }
.buscard.done .bc-count { color: var(--green); }
.buscard.locked { background: #f4f6fa; }
.buscard.locked .bc-name { color: var(--ink-2); }
.bc-flag { font-size: 12px; font-weight: 800; color: var(--amber); }

/* ---- student rows ----
   46px, down from 68 (task 3106: 50) then 50 (task 3109: 46). The air went
   first: a 68px row held a 36px circle and a 22px name, so a third of every row
   was empty, and over 34 children that third was a whole screenful.

   THE ROW HEIGHT IS SET BY THE BOARDED ROW, NOT BY THE NAME. A row that grows
   the moment it is tapped moves every name below it while a thumb is on its
   way to the next one, so the height has to be the height of the row AFTER it
   turns green - name plus "Aboard 3:52 - Mrs Quint":

       padding 5 + name 15x1.2 = 18 + 1 + meta 12.5x1.3 = 16.25 + padding 5
       = 45.25  ->  46px

   That is 3106's own arithmetic (which gave 50 at a 17px name) re-run at 15px.
   Measured against Blue 3:45's real 34 children: list height 1565px whether
   nobody is aboard or all 34 are, and 0 of 34 rows change height when tapped.

   NOT 44. The two things directly above and below a row are also tap targets
   and the cost of landing on the wrong one is a child marked aboard who is not
   - the one error on this page that does not announce itself. 46px is ~12.2mm,
   comfortably past both the WCAG 2.5.5 floor and the ~9.2mm one-handed thumb
   target the HCI literature puts on serial tapping, and it keeps two pixels in
   hand. If the field says otherwise, this number and the name size below are
   the two to move - together, or the boarded row starts growing again. */
.srows { background: var(--card); border-bottom: 1px solid var(--line); }
.srow {
  display: grid; grid-template-columns: 30px 1fr; align-items: center; gap: 11px;
  width: 100%; text-align: left; min-height: 46px; padding: 5px 12px;
  background: var(--card); color: var(--ink);
  border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
  font-size: 15px; font-weight: 600;
}

/* ---- the second column: a TABLET affordance, not a phone one (task 3109) ----
   Danny asked for two columns "on one screen". On a tablet that is free money
   and it is switched on here. On a phone it was measured and refused, and the
   numbers are in the file header: at 375px each column gives the name 122px,
   the longest name on the real 10/15 roster renders 190px wide at 15px, and
   245 of 313 real names break mid-word. Two columns there does not buy a
   teacher less scrolling, it buys her half a name per line.

   600px, not 540. The breakpoint is set by the LONGEST NAME, not by the device
   class: the name box is `viewport / 2 - 65`, so the roster stops wrapping at
   ~511px. 540 would sit two characters above that cliff; 600 gives the column
   a 235px name box - about 45px, six characters, of headroom over the longest
   name the school has actually sent us. Every phone in portrait is <= 430px,
   so no phone comes near it, and a phone in LANDSCAPE (>= 667px, and only
   ~375px tall) is exactly the case that needs the second column most.

   COLUMN-MAJOR, which is what multi-column gives us and what a phone book
   gives a reader: alphabetical still runs straight down column one and on down
   column two. That also keeps the mis-tap honest - the neighbour above and
   below a row is still the neighbour in the alphabet, so a slip still lands
   somewhere the teacher is already looking. The other column is 359px away,
   which is not a slip.

   `break-inside: avoid` is load-bearing, not tidiness: without it a row
   carrying a change-details block splits across the column boundary and one
   child appears in two places. */
@media (min-width: 600px) {
  .srows { column-count: 2; column-gap: 1px; column-rule: 1px solid var(--line); }
  .srows .srow { break-inside: avoid; }
  /* "Everyone is aboard" / "Nobody on this roster" is a sentence about the
     whole list, so it must not be dealt into one half of it. */
  .srows .empty { column-span: all; }
}
.srow:last-child { border-bottom: 0; }
.srow > span:nth-child(2) { display: block; min-width: 0; }
.srow .nm, .srow .meta { display: block; overflow-wrap: anywhere; }
.srow .mark {
  width: 30px; height: 30px; border-radius: 50%;
  border: 2px solid #9aa8bd; background: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; font-weight: 900; color: transparent;
}
/* 15px, down from 17 (task 3109). Danny, on a real phone: "way too big still."
   15px BOLD on near-white is the same size the Not Joining list has always
   used, it is 12% off a size that was already chosen for sunlight, and the
   longest name on the real roster still renders 190px inside a 310px box, so
   nothing wraps in one column. This is the smallest I will go without a report
   from the field: the reader is outdoors at arm's length, and she is MATCHING
   a name she is already looking for rather than reading it cold, which is the
   only reason 15px is enough. If Eli says it is small in the sun, move this
   and `.srow`'s min-height back together. */
.srow .nm { font-size: 15px; font-weight: 700; line-height: 1.2; }
.srow .meta { font-size: 12.5px; font-weight: 600; color: var(--muted); margin-top: 1px; }
.srow.aboard { background: var(--green-bg); }
.srow.aboard .mark { background: var(--green); border-color: var(--green); color: #fff; }
.srow.aboard .meta { color: #2b6b45; }
.srow[disabled] { opacity: 1; }
.srow.saving .mark { border-style: dashed; }
.srow .meta .sv { color: var(--amber); font-weight: 800; }
/* Used by roster rows, Not Joining rows and the off-roster list alike - scoping
   it to .srow is how it silently disappeared from the other two. */
.cls {
  display: inline-block; font-size: 12px; font-weight: 800; color: var(--ink-2);
  background: #e8edf5; border-radius: 6px; padding: 1px 6px; margin-left: 6px;
  vertical-align: 2px;
}
/* 'New' / 'Change' rows carry the reason inline - a teacher must not have to
   tap anything to find out that this child is only on the bus today. */
.chg {
  display: block; margin-top: 4px; padding: 5px 8px;
  background: var(--amber-bg); border: 1px solid var(--amber-line); border-radius: 9px;
  font-size: 13px; font-weight: 600; color: #7c3f06; line-height: 1.3;
  white-space: pre-line; overflow-wrap: anywhere;
}
.chg b { font-weight: 900; text-transform: uppercase; font-size: 11.5px; letter-spacing: .04em; }

/* ---- two children, one name (task 3091, QA defect D1) ----
   ITS OWN COLOUR, on purpose. Amber on this page means "this child's
   arrangement is different today" (.chg) and red means something failed; a
   duplicated name is neither - it is a statement about identity, and it must
   not read as the same kind of thing as the amber block that can sit directly
   above it on the same row. Indigo on the near-paper surface holds ~8:1, which
   is what this page exists for.
   The ONE case that escalates to the amber the rest of the page uses for
   caution is `certain:false` - the day has been re-imported, so which child a
   row means can no longer be confirmed from the data at all. */
.dup {
  display: block; margin-top: 4px; padding: 6px 8px;
  background: #eef1fe; border: 1px solid #c3cbf6; border-radius: 9px;
  font-size: 13px; font-weight: 600; color: #2f3a86; line-height: 1.35;
  overflow-wrap: anywhere;
}
.dup b {
  display: block; font-weight: 900; text-transform: uppercase;
  font-size: 11.5px; letter-spacing: .04em;
}
.dup .dup-l { display: block; margin-top: 4px; white-space: pre-line; }
.dup .dup-k {
  display: block; font-weight: 900; font-size: 11.5px;
  text-transform: uppercase; letter-spacing: .03em;
}
/* "The roster differs on nothing" is the sentence a teacher must not skim. */
.dup .dup-none { font-weight: 800; }
.dup.dup-uncertain {
  background: var(--amber-bg); border-color: var(--amber-line); color: #7c3f06;
}
/* The picker's own flag, in the row block's colour rather than the amber the
   other flags use - so a card carrying both does not read as one sentence. */
.bc-flag.bc-dup { color: #3730a3; }

/* ---- panels / sections ----
   These sit BELOW the roster, so they buy no extra rows - but they are part of
   "scroll down to see the whole thing" (task 3106), and the note field and the
   depart button are the end of that scroll. Tighter, not smaller: the textarea
   and both buttons keep their sizes. */
.panel { background: var(--card); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 11px 12px; margin-top: 9px; }
.panel-h { font-size: 15px; margin: 0 0 3px; text-transform: none; letter-spacing: 0; }
.panel-p { font-size: 12.5px; color: var(--muted); margin: 0 0 8px; }
body.att-body #note {
  width: 100%; background: #fff; color: var(--ink); border: 1px solid #9aa8bd;
  border-radius: 10px; padding: 12px; font-size: 16px; line-height: 1.4; resize: vertical;
}
.note-row { display: flex; align-items: center; gap: 12px; margin-top: 9px; }
.btn-wide { min-height: 48px; padding: 0 20px; font-size: 16px; background: var(--amber-solid); color: #26190a; }
.note-state { font-size: 13px; font-weight: 700; color: var(--muted); }
.note-state.ok { color: var(--green); }
.note-state.err { color: var(--red); }

.collapse {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  min-height: 48px; padding: 9px 12px; text-align: left;
  background: var(--card); color: var(--ink); border: 0; border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line); border-radius: 0; font-size: 16px; font-weight: 800;
}
.collapse .chev { color: var(--muted); font-size: 14px; font-weight: 700; }
.njrow {
  display: grid; grid-template-columns: 1fr; gap: 2px;
  padding: 8px 12px; border-bottom: 1px solid var(--line); background: #f8fafc;
}
/* Same size as a roster name (task 3109). These are the same kind of thing -
   a child's name being read off a list - and leaving this at 15.5 while the
   roster came down to 15 would have made the collapsed list the louder of the
   two, which is backwards. */
.njrow .nm { font-size: 15px; font-weight: 700; }
.njrow .why { font-size: 13px; color: var(--ink-2); white-space: pre-line; overflow-wrap: anywhere; }

/* ---- off-roster: visible, never hidden ---- */
.offrow {
  display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 12px;
  padding: 9px 12px; border-bottom: 1px solid var(--amber-line); background: var(--amber-bg);
}
.offrow > span:first-child { min-width: 0; }
.offrow .nm, .offrow .why { display: block; overflow-wrap: anywhere; }
.offrow .nm { font-size: 16.5px; font-weight: 700; }
.offrow .why { font-size: 12.5px; color: #7c3f06; font-weight: 600; margin-top: 2px; }
.offrow button { min-height: 46px; padding: 0 14px; font-size: 14px; background: #fff; color: var(--amber); border: 2px solid var(--amber-line); }

/* ---- depart ----
   The one control on the page that deliberately did NOT shrink much (task
   3106). It arms on the first tap and fires on the second, a mis-fire locks
   the run, and only an admin can reopen it - so it keeps being the biggest
   button on the screen. */
.depart-panel { margin-top: 14px; }
.btn-depart {
  width: 100%; min-height: 56px; font-size: 19px; font-weight: 800;
  background: var(--brand); color: #fff; border-radius: 14px;
}
.btn-depart.armed { background: var(--red); }
.btn-depart[disabled] { background: #c7d0dd; color: #5b6b85; }
.locked-card {
  margin: 9px 12px; padding: 12px; border-radius: 14px;
  background: #f4f6fa; border: 1px solid var(--line);
}
.locked-card h2 { font-size: 17px; margin: 0 0 3px; }
.locked-card p { font-size: 13.5px; color: var(--ink-2); margin: 3px 0 0; }
.locked-card button { margin-top: 12px; min-height: 48px; font-size: 15px; }

/* ---- banners ---- */
.att-banner {
  margin: 8px 12px; padding: 10px 12px; border-radius: 12px;
  font-size: 14px; font-weight: 700; line-height: 1.35; overflow-wrap: anywhere;
  display: flex; gap: 10px; align-items: flex-start;
}
.att-banner.warn { background: var(--amber-bg); border: 1px solid var(--amber-line); color: #7c3f06; }
.att-banner.err { background: var(--red-bg); border: 1px solid #f0a8a2; color: var(--red); }
.att-banner.info { background: #eef4ff; border: 1px solid #bcd0f7; color: #1e3a8a; }
.att-banner.ok { background: var(--green-bg); border: 1px solid var(--green-line); color: var(--green); }
.att-banner button {
  /* 44px as of task 3090: "Retry now" on a failing save is the last control a
     teacher has before she walks away with unsaved taps, and it was the other
     thing on this page sitting under the line it set for itself. */
  margin-left: auto; flex: 0 0 auto; background: rgba(255,255,255,.7); color: inherit;
  border: 1px solid currentColor; min-height: 44px; padding: 0 12px; font-size: 13px;
}
.empty { padding: 22px 18px; text-align: center; color: var(--muted); font-size: 15px; }
.empty b { display: block; color: var(--ink); font-size: 17px; margin-bottom: 5px; }

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