/* BehaviorDash — per-surface token adoption (v173)
 *
 * Loaded last. Each surface is converted here as it is rebuilt, rather than
 * rewriting admin.css in one sweep: the legacy rules stay intact and testable,
 * and every conversion is a reversible block.
 *
 * The type scale could not land globally in Foundations because lifting a 9px
 * label to 12px reflows what sits around it. This is where that lands, one
 * surface at a time.
 */

/* ═══ Audit Log ═══════════════════════════════════════════════════════════
 * Before: 46 distinct hardcoded greys, 26 of them below 4.5:1, carrying text
 * at 8-10px. axe measured 15 contrast failures in this pane alone, the worst
 * at 2.19:1 on 10px. Every muted value below now resolves to --ink-tertiary
 * (5.48:1) or --ink-secondary (7.33:1), and every size sits on the scale.
 */
#tab-audit .audit-page-head p{color:var(--ink-tertiary);font-size:var(--fs-footnote);line-height:var(--lh-body)}
#tab-audit .audit-kicker{color:var(--ink-tertiary);font-size:var(--fs-micro);letter-spacing:var(--tracking-micro)}
#tab-audit .audit-total span{color:var(--ink-tertiary);font-size:var(--fs-caption)}
#tab-audit .audit-toolbar label>span{color:var(--ink-secondary);font-size:var(--fs-caption);font-weight:var(--fw-semi)}
#tab-audit .audit-search i{color:var(--ink-tertiary)}
#tab-audit .audit-day{color:var(--ink-secondary);font-size:var(--fs-caption);font-weight:var(--fw-bold);
  letter-spacing:var(--tracking-micro)}
#tab-audit .audit-row time{color:var(--ink-tertiary);font-size:var(--fs-caption);
  font-variant-numeric:tabular-nums}
#tab-audit .audit-row-main span{color:var(--ink-tertiary);font-size:var(--fs-caption)}
#tab-audit .audit-row-main b{font-size:var(--fs-footnote);color:var(--ink-primary)}
#tab-audit .audit-chevron{color:var(--ink-tertiary);font-size:var(--fs-footnote)}
#tab-audit .audit-loading,
#tab-audit .audit-empty{color:var(--ink-tertiary);font-size:var(--fs-footnote)}
#tab-audit .audit-empty i{color:var(--line-strong)}
#tab-audit .audit-empty b{color:var(--ink-primary);font-size:var(--fs-headline)}
#tab-audit .audit-range{color:var(--ink-tertiary);font-size:var(--fs-caption)}
#tab-audit .audit-range b{color:var(--ink-primary)}
#tab-audit .audit-ellipsis{color:var(--ink-tertiary);font-size:var(--fs-footnote)}
#tab-audit .audit-page-size{color:var(--ink-tertiary);font-size:var(--fs-caption)}
/* Every declaration on the legacy .audit-view-toggle button is !important, so
   these must be too. The alternative is renaming a class the v164/v165 harnesses
   assert on, which would trade a styling fix for a test rewrite. */
#tab-audit .audit-view-toggle button{
  color:var(--ink-secondary)!important;font-size:var(--fs-caption)!important;
  height:auto!important;min-height:var(--target-dense)!important;
  padding:0 var(--s-3)!important;font-weight:var(--fw-semi)!important}
#tab-audit .audit-view-toggle button.active{color:var(--accent-on-soft)!important}
/* The star marking "important changes" was 8px at 2.62:1 — decorative size on
   a meaning-bearing icon. Colour alone never carries this: the label says it. */
#tab-audit .audit-view-toggle button i{color:var(--warning);font-size:var(--fs-caption)}
#tab-audit .audit-category{font-size:var(--fs-caption);min-height:var(--target-dense)}

/* The drawer is now the shared detail column; these keep its inner type on the
   scale wherever it renders. */
.audit-drawer-close,#detail-pane .audit-drawer-close{color:var(--ink-secondary)}
.audit-drawer header p,#detail-pane .audit-detail-when{color:var(--ink-tertiary);font-size:var(--fs-caption)}
.audit-detail-meta span{color:var(--ink-tertiary);font-size:var(--fs-micro);
  letter-spacing:var(--tracking-micro);text-transform:uppercase;font-weight:var(--fw-bold)}
.audit-detail-meta b{color:var(--ink-primary);font-size:var(--fs-footnote)}
.audit-change>b{color:var(--ink-secondary);font-size:var(--fs-caption);font-weight:var(--fw-semi)}
.audit-change i{color:var(--ink-tertiary)}
.audit-no-changes{color:var(--ink-tertiary);font-size:var(--fs-footnote)}
.audit-technical summary{color:var(--ink-secondary);font-size:var(--fs-footnote);
  min-height:var(--target-dense);display:flex;align-items:center}
.audit-technical dt{color:var(--ink-tertiary);font-size:var(--fs-caption)}
.audit-technical dd{color:var(--ink-primary);font-size:var(--fs-caption)}

/* Rows are the primary target on this surface: one per record, clicked all day
   by someone reconstructing what happened. */
#tab-audit .audit-row{min-height:var(--target-dense);
  transition:background var(--dur-quick) var(--ease-snappy)}
#tab-audit .audit-row[aria-current="true"]{background:var(--accent-soft)}
#tab-audit .audit-pagination button{min-height:var(--target-dense);min-width:var(--target-dense)}

/* Detail column presentation of an audit record. */
#detail-pane .audit-detail-head{display:flex;flex-direction:column;gap:var(--s-2);
  padding-bottom:var(--s-3);border-bottom:1px solid var(--line-subtle);margin-bottom:var(--s-4)}
#detail-pane .audit-detail-head h2{font-family:var(--font-brand);font-size:var(--fs-title-3);
  font-weight:var(--fw-semi);color:var(--ink-primary);margin:0;line-height:var(--lh-tight)}
#detail-pane .audit-detail-section{margin-bottom:var(--s-4)}
#detail-pane .audit-detail-section h3{font-size:var(--fs-micro);font-weight:var(--fw-bold);
  letter-spacing:var(--tracking-micro);text-transform:uppercase;color:var(--ink-tertiary);
  margin:0 0 var(--s-2)}
#detail-pane .audit-detail-meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));
  gap:var(--s-3);margin-bottom:var(--s-4)}
#detail-pane .audit-detail-meta>div{display:flex;flex-direction:column;gap:2px}

/* The close button carries its own class rather than the legacy .audit-drawer-close,
   whose every declaration is !important and positioned for a drawer corner. */
#detail-pane .audit-detail-close{margin-top:var(--s-4)}

/* The toolbar is a grid with fixed tracks sized for a full-width pane, so it
   clipped its last control once the detail column narrowed the content. auto-fit
   lets it reflow at any container width instead. */
#tab-audit .audit-toolbar{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}
#tab-audit .audit-toolbar>label.audit-search{grid-column:1/-1}
#tab-audit .audit-toolbar select,
#tab-audit .audit-toolbar input{width:100%;min-width:0;box-sizing:border-box}
#tab-audit .audit-filter-strip{flex-wrap:wrap;gap:var(--s-2)}
#tab-audit .audit-categories{flex-wrap:wrap}

/* ═══ Review ══════════════════════════════════════════════════════════════
 * Already at 0 axe violations after the global control floor raised the
 * checklist from 13px to 18px. This lands the surface on the type scale and
 * gives the queue the same row rhythm as the audit list.
 */
#tab-review .card h2{font-family:var(--font-brand);font-size:var(--fs-title-2);
  font-weight:var(--fw-semi);color:var(--ink-primary)}
#tab-review .muted{color:var(--ink-tertiary)}
#tab-review table td{padding:var(--s-3) var(--s-4);vertical-align:top}
#tab-review table th{font-size:var(--fs-micro);letter-spacing:var(--tracking-micro);
  text-transform:uppercase;color:var(--ink-tertiary);padding:var(--s-3) var(--s-4)}
/* The comment box: 44px of monospace before the textarea rule landed. */
#tab-review textarea{font-family:var(--font-ui);font-size:var(--fs-footnote);
  min-height:72px;border:1px solid var(--line-default);border-radius:var(--r-md);
  padding:var(--s-2) var(--s-3);line-height:var(--lh-body);color:var(--ink-primary);
  background:var(--bg-raised);resize:vertical;box-sizing:border-box}
#tab-review textarea:focus{border-color:var(--accent-primary);
  box-shadow:var(--focus-glow);outline:none}
/* Each checklist item is its own target, not a 13px box with a label beside it. */
#tab-review label{min-height:var(--target-dense);border-radius:var(--r-sm);
  padding:0 var(--s-1);transition:background var(--dur-quick) var(--ease-snappy)}
#tab-review label:hover{background:var(--bg-sunken)}
/* A row leaving the queue after an optimistic approve should not simply blink
   out; it settles, which is the only motion on this surface. */
#tab-review tr[data-review-row]{transition:opacity var(--dur-snappy) var(--ease-snappy)}
#tab-review tr[data-review-row][hidden]{display:none}

/* ═══ Roster surfaces — Students · Staff · Assignments ════════════════════
 * Before: 3.3 buttons per student row, eight of them the same neutral grey,
 * no search anywhere, and inactive rows dimmed to opacity .55 -- which pushed
 * four cells below 4.5:1. Conveying state by making it harder to read fails
 * both 1.4.1 and 1.4.3, and the status is already stated in its own column.
 */
.roster-toolbar{display:flex;gap:var(--s-3);align-items:center;flex-wrap:wrap;margin:0 0 var(--s-3)}
.roster-search{flex:1 1 260px;min-width:0;position:relative;display:flex}
.roster-search input{
  width:100%;min-height:var(--target-dense);
  font-family:var(--font-ui);font-size:var(--fs-footnote);color:var(--ink-primary);
  background:var(--bg-raised);border:1px solid var(--line-default);border-radius:var(--r-md);
  padding:0 var(--s-3);box-sizing:border-box;
  transition:border-color var(--dur-quick) var(--ease-snappy),box-shadow var(--dur-quick) var(--ease-snappy)}
.roster-search input:hover{border-color:var(--line-strong)}
.roster-search input:focus{border-color:var(--accent-primary);
  box-shadow:var(--focus-glow);outline:none}
.roster-search input::placeholder{color:var(--ink-tertiary)}

/* Inactive rows keep full text contrast. The row reads quieter through its
   surface, not through dimmed type, and the status pill carries the meaning. */
#tab-students .row-inactive,
#tab-staff .row-inactive{background:var(--bg-sunken)}
#tab-students .row-inactive td,
#tab-staff .row-inactive td{color:var(--ink-secondary)}

#tab-students table td,#tab-staff table td,#tab-assignments table td{
  padding:var(--s-3) var(--s-4);vertical-align:middle}
#tab-students table th,#tab-staff table th,#tab-assignments table th{
  font-size:var(--fs-micro);letter-spacing:var(--tracking-micro);text-transform:uppercase;
  color:var(--ink-tertiary);padding:var(--s-3) var(--s-4)}
#tab-students .muted,#tab-staff .muted,#tab-assignments .muted{color:var(--ink-tertiary)}
#tab-students select,#tab-staff select{min-height:var(--target-dense);font-size:var(--fs-caption)}

/* ═══════════════════════════════════════════════════════════════════════
   Reports
   The Report Center is the one surface an admin arrives at with a question
   already formed ("how is this student doing?"), so it is laid out as a
   choice, then a form, then the answer - three steps, one visible at a
   time. The pickers were click-wired <div>s; they are now buttons, which
   is why everything here styles a <button> back into a card.
   ═══════════════════════════════════════════════════════════════════════ */
/* One page header for the whole product. Measured across ten surfaces, the
   page title was appearing at six different treatments — 16px/600, 18px/600,
   24px/600, 24px/700, 26px/600 and 28px/700 — four of which are not steps on
   the type scale at all. Any surface that adopts .page-title / .page-sub now
   gets the same one. (Five surfaces still carry their title inside the first
   card instead of above the content; unifying those is a structural change,
   not a token swap, and is tracked separately.) */
.page-title{font-family:var(--font-brand);font-size:var(--fs-title-2);
  font-weight:var(--fw-bold);color:var(--ink-primary);letter-spacing:var(--tracking-display);
  line-height:var(--lh-tight);margin:0 0 var(--s-1)}
.page-sub{font-size:var(--fs-footnote);color:var(--ink-secondary);
  line-height:var(--lh-body);margin:0 0 var(--s-5);max-width:68ch}

.report-custom-card{display:flex;gap:var(--s-3);align-items:flex-start;width:100%;
  text-align:left;padding:var(--s-4);margin:0 0 var(--s-5);cursor:pointer;
  background:var(--bg-raised);border:1.5px dashed var(--line-strong);
  border-radius:var(--r-lg);color:var(--ink-primary);font-family:inherit;
  transition:border-color var(--dur-quick) var(--ease-snappy),
             background var(--dur-quick) var(--ease-snappy),
             transform var(--dur-quick) var(--ease-snappy);}
.report-custom-card:hover:not(:disabled){border-color:var(--accent-primary);background:var(--accent-soft)}
.report-custom-card:active{transform:scale(var(--lift-press))}
.report-custom-card>.bi{font-size:20px;line-height:1;color:var(--accent-primary);
  margin-top:1px;flex:0 0 auto}
.report-custom-card__text{display:flex;flex-direction:column;gap:var(--s-1);min-width:0}
.report-custom-card__text b{font-size:var(--fs-headline);font-weight:var(--fw-semi)}
/* admin.css sets a bare button{font-weight:600}, which every card-shaped
   button here inherits. Body copy inside one has to say otherwise or the
   whole description renders bold. */
.report-custom-card__text .muted{font-size:var(--fs-caption);color:var(--ink-secondary);
  line-height:var(--lh-body);font-weight:var(--fw-regular)}

.report-pick-head{font-size:var(--fs-micro);letter-spacing:var(--tracking-micro);
  text-transform:uppercase;color:var(--ink-tertiary);font-weight:var(--fw-bold);
  margin:0 0 var(--s-3)}
.report-pick-grid{display:grid;gap:var(--s-3);
  grid-template-columns:repeat(auto-fill,minmax(232px,1fr))}
.report-pick{display:flex;flex-direction:column;gap:var(--s-1);align-items:flex-start;
  text-align:left;padding:var(--s-4);cursor:pointer;font-family:inherit;
  background:var(--bg-raised);border:1px solid var(--line-default);
  border-radius:var(--r-lg);box-shadow:var(--elev-1);color:var(--ink-primary);
  min-height:var(--target-touch);
  transition:border-color var(--dur-quick) var(--ease-snappy),
             box-shadow var(--dur-quick) var(--ease-snappy),
             transform var(--dur-quick) var(--ease-snappy);}
/* background is declared explicitly, not inherited: a specificity tie only
   helps for properties this rule actually sets. Without it the global
   button:hover paints #10243E behind text that is also #10243E. */
.report-pick:hover:not(:disabled){border-color:var(--accent-primary);box-shadow:var(--elev-2);
  background:var(--accent-soft);transform:translateY(var(--lift-hover))}
.report-pick:active{transform:scale(var(--lift-press))}
.report-pick__title{font-size:var(--fs-headline);font-weight:var(--fw-semi);
  line-height:var(--lh-tight)}
.report-pick__desc{font-size:var(--fs-caption);color:var(--ink-secondary);
  line-height:var(--lh-body);font-weight:var(--fw-regular)}
/* An unavailable report is named, not dimmed. Dimming would drop both the
   contrast and the only cue at once - 1.4.3 and 1.4.1 in a single stroke. */
.report-pick[disabled]{cursor:default;background:var(--bg-sunken);
  border-style:dashed;box-shadow:none;transform:none}
.report-pick[disabled]:hover{border-color:var(--line-default);box-shadow:none;transform:none}
.report-pick__soon{background:var(--bg-inset);color:var(--ink-secondary);
  font-size:var(--fs-micro);font-weight:var(--fw-bold);border-radius:var(--r-pill);
  padding:2px var(--s-2);margin-left:var(--s-1);white-space:nowrap}

#tab-reports .rpt-field{display:flex;flex-direction:column;gap:var(--s-1);
  font-size:var(--fs-caption);font-weight:var(--fw-semi);color:var(--ink-secondary)}
#tab-reports .rpt-field select,#tab-reports .rpt-field input{
  min-height:var(--target-touch);font-family:var(--font-ui);
  font-size:var(--fs-footnote);color:var(--ink-primary)}
#tab-reports .row-form{display:flex;flex-wrap:wrap;gap:var(--s-3);align-items:flex-end}

/* ── The de-identify choice, on both report paths ──────────────────────────
 * Reports are the surface that leaves the building — printed for an IEP
 * meeting, saved as a PDF, emailed to a parent. That is where the FERPA risk
 * actually is, so the control sits beside Generate rather than being a mode
 * buried in Settings. It reads as a decision, not a nit: full-size target,
 * a real explanation under the label, and a visible box rather than a
 * switch, because "off" has to be unmistakable. */
.rpt-deid{display:flex;align-items:flex-start;gap:var(--s-2);
  max-width:20rem;padding:var(--s-2) var(--s-3);
  border:1px solid var(--line-default);border-radius:var(--r-md);
  background:var(--bg-inset);cursor:pointer}
.rpt-deid input{flex:0 0 auto;width:1.125rem;height:1.125rem;margin-top:1px;
  min-height:0;accent-color:var(--brand)}
.rpt-deid span{display:flex;flex-direction:column;gap:2px;
  font-size:var(--fs-footnote);font-weight:var(--fw-semi);color:var(--ink-primary)}
.rpt-deid small{font-weight:var(--fw-regular);font-size:var(--fs-caption);
  color:var(--ink-secondary);line-height:1.35}
.rpt-deid:hover{border-color:var(--brand);background:var(--bg-inset)}
.rpt-deid:focus-within{outline:2px solid var(--focus-ring);outline-offset:2px}

/* ── Settings → Students & privacy ─────────────────────────────────────────
 * The same de-identify control appears here as the organization default, so
 * .rpt-deid is deliberately reused rather than duplicated — one control, one
 * look, wherever the question is asked. Given room here, it may run wider. */
.settings-section-card .rpt-deid{max-width:34rem}

.student-display-set{border:0;padding:0;margin:0 0 var(--s-5)}
.student-display-set legend{padding:0;margin-bottom:var(--s-2);
  font-size:var(--fs-footnote);font-weight:var(--fw-bold);color:var(--ink-primary)}
.student-display-note{margin:var(--s-2) 0 var(--s-3);max-width:44rem;
  font-size:var(--fs-caption);color:var(--ink-secondary);line-height:1.45}

/* Selected state is fill, ink and weight — the way a macOS list row reads.
 * No accent bar, no bracket, no sliver clinging to an edge. */
.student-display-option{display:flex;align-items:flex-start;gap:var(--s-3);
  padding:var(--s-3);margin-bottom:var(--s-2);max-width:44rem;
  border:1px solid var(--line-default);border-radius:var(--r-md);
  background:var(--bg-raised);cursor:pointer}
.student-display-option input{flex:0 0 auto;width:1.125rem;height:1.125rem;
  margin-top:2px;min-height:0;accent-color:var(--brand)}
.student-display-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
.student-display-copy b{font-size:var(--fs-footnote);font-weight:var(--fw-semi);
  color:var(--ink-primary)}
.student-display-copy small{font-size:var(--fs-caption);color:var(--ink-secondary);
  line-height:1.4}
.student-display-option.is-on{background:var(--bg-inset);border-color:var(--brand)}
.student-display-option.is-on .student-display-copy b{font-weight:var(--fw-bold)}
.student-display-option:hover{border-color:var(--brand)}
.student-display-option:focus-within{outline:2px solid var(--focus-ring);outline-offset:2px}

/* The preview of the choice. Monospaced so a code reads as a code, and set
 * on its own line so it is plainly a sample rather than more prose. */
.student-display-sample{margin-top:var(--s-1);align-self:flex-start;
  padding:2px var(--s-2);border-radius:var(--r-sm);
  background:var(--bg-sunken);border:1px solid var(--line-subtle);
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:var(--fs-caption);color:var(--ink-primary);
  overflow-wrap:anywhere}

@media (max-width:640px){
  .student-display-option,.student-display-note,.settings-section-card .rpt-deid{max-width:none}
}

/* ── Settings → Security ───────────────────────────────────────────────────
 * The state line has to be readable at a glance, because the question it
 * answers ("is my account actually protected?") is one people ask in a hurry
 * before a meeting. State is carried by fill and an icon, not by a coloured
 * edge — same rule as everywhere else here. */
.sec-state{display:flex;align-items:flex-start;gap:var(--s-3);max-width:44rem;
  padding:var(--s-3);border:1px solid var(--line-default);border-radius:var(--r-md);
  background:var(--bg-inset);margin-bottom:var(--s-3)}
.sec-state i{font-size:1.25rem;line-height:1.2;color:var(--ink-secondary)}
.sec-state div{display:flex;flex-direction:column;gap:2px;min-width:0}
.sec-state b{font-size:var(--fs-footnote);font-weight:var(--fw-semi);color:var(--ink-primary)}
.sec-state small{font-size:var(--fs-caption);color:var(--ink-secondary);line-height:1.4}
/* "On" deliberately keeps the SAME panel as "off". The first version tinted
 * it with --ok-bg, which is defined once with no dark-mode value: in dark
 * mode that put a light-green card in a dark UI and dropped the subtitle to
 * 1.70:1, because the rule coloured the <b> and left <small> inheriting the
 * default ink. Measuring caught it; reading the CSS would not have.
 *
 * So the state is carried by the icon and the weight of the sentence, on a
 * background already known to pass in both themes — which is also the rule
 * the rest of this app follows for selected and active states. */
 * The icon keeps the panel's own ink rather than a green. --ok-text is a dark
 * green defined once with no dark-theme value, which measured 2.63:1 on the
 * dark panel — below the 3:1 floor for a graphic that carries meaning. The
 * shield-check glyph and the bold sentence say "on" perfectly well at an ink
 * that is legible in both themes, and inventing a token for one icon is not
 * worth a thirteenth colour in a file that already has too many. */
.sec-state--on b{font-weight:var(--fw-bold)}

.sec-factors{list-style:none;margin:0 0 var(--s-3);padding:0;max-width:44rem}
.sec-factors li{display:flex;align-items:center;justify-content:space-between;gap:var(--s-3);
  padding:var(--s-2) var(--s-3);border:1px solid var(--line-subtle);
  border-radius:var(--r-md);background:var(--bg-raised);margin-bottom:var(--s-2)}
.sec-factors span{display:flex;flex-direction:column;gap:2px;min-width:0}
.sec-factors b{font-size:var(--fs-footnote);color:var(--ink-primary)}
.sec-factors small{font-size:var(--fs-caption);color:var(--ink-secondary)}

.sec-enroll{max-width:44rem;margin-top:var(--s-3);padding:var(--s-4);
  border:1px solid var(--line-default);border-radius:var(--r-md);background:var(--bg-raised)}
.sec-enroll__steps{margin:0 0 var(--s-3);padding-left:1.25rem;
  font-size:var(--fs-footnote);color:var(--ink-secondary);line-height:1.5}
.sec-enroll__steps li{margin-bottom:var(--s-1)}
/* The QR is given a white plate regardless of theme: a dark-mode inversion
 * makes it unreadable to a phone camera. */
.sec-enroll__qr{display:block;width:180px;height:180px;margin:0 0 var(--s-3);
  padding:var(--s-2);background:#fff;border:1px solid var(--line-default);border-radius:var(--r-sm)}
.sec-enroll__key{display:flex;flex-direction:column;gap:var(--s-1);margin-bottom:var(--s-3)}
.sec-enroll__key span{font-size:var(--fs-caption);font-weight:var(--fw-semi);color:var(--ink-secondary)}
.sec-enroll__key code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:var(--fs-footnote);letter-spacing:.06em;color:var(--ink-primary);
  padding:var(--s-2);background:var(--bg-sunken);border:1px solid var(--line-subtle);
  border-radius:var(--r-sm);overflow-wrap:anywhere;user-select:all}

/* ── The second-factor prompt at sign-in ───────────────────────────────────
 * A native <dialog>, so the focus trap and backdrop are the browser's. The
 * code field is big and monospaced because it is typed from another device,
 * under mild time pressure — the code expires in 30 seconds. */
dialog.bd-mfa{border:0;padding:0;background:transparent;max-width:min(26rem,calc(100vw - 2rem))}
dialog.bd-mfa::backdrop{background:rgba(9,18,26,.72)}
dialog.bd-mfa form{background:var(--bg-raised);color:var(--ink-primary);
  border-radius:var(--r-lg);padding:var(--s-5);box-shadow:var(--shadow-lg);
  display:flex;flex-direction:column;gap:var(--s-3)}
dialog.bd-mfa h2{margin:0;font-size:var(--fs-title-3);color:var(--ink-primary)}
dialog.bd-mfa p{margin:0;font-size:var(--fs-footnote);color:var(--ink-secondary);line-height:1.45}
dialog.bd-mfa .bd-mfa__field input{width:100%;box-sizing:border-box;
  min-height:var(--target-touch);padding:var(--s-2) var(--s-3);
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:1.5rem;letter-spacing:.35em;text-align:center;
  color:var(--ink-primary);background:var(--bg-inset);
  border:1px solid var(--line-default);border-radius:var(--r-md)}
dialog.bd-mfa .bd-mfa__field input:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
dialog.bd-mfa .bd-mfa__msg:empty{display:none}
dialog.bd-mfa .bd-mfa__msg{color:var(--error-text);font-weight:var(--fw-semi)}
dialog.bd-mfa .bd-mfa__actions{display:flex;justify-content:flex-end;gap:var(--s-2);flex-wrap:wrap}

/* OWNER PRODUCT REVIEW, finding 4. Two halves, not three peers: the way
   back on the left, the things you can do with this report on the right.
   space-between across THREE children is what pushed one export button into
   the middle of the row with nothing either side of it; across two, it does
   exactly what it should. align-items:flex-start so a wrapped export pair
   does not stretch the Back button to match its height. */
.report-screen__bar{display:flex;flex-wrap:wrap;gap:var(--s-2);
  align-items:flex-start;justify-content:space-between;margin-bottom:var(--s-4)}
/* The gap is the grouping: print and the export pair are separated by more
   space than the two exports are from each other, so the pair reads as one
   thing without a rule, a border or a divider. */
/* V250: a column of two things - the row of actions, and the sentence that
   says which export is which. A column, not a wrapped row: a flex item with
   a percentage basis makes its container size to the item's MAX-CONTENT,
   so an explanatory sentence in the same row would have made this box wider
   than its own buttons and pushed the whole bar onto a second line. */
.report-screen__actions{display:flex;flex-direction:column;align-items:flex-end;
  gap:var(--s-2)}
.report-screen__action-row{display:flex;flex-wrap:wrap;align-items:center;
  gap:var(--s-2) var(--s-5)}
.report-screen__exports{display:flex;flex-wrap:wrap;gap:var(--s-2)}
/* V250: which export is which, said in text because there is no hover on an
   iPad. It sits on its own line INSIDE the actions half - the bar stays two
   halves. max-width is load-bearing, not taste: a flex item's max-content
   width is what a wrapping container sizes itself to, so an uncapped
   sentence here would make the actions box wider than its own buttons and
   push the whole bar onto a second line. 46ch keeps it narrower than the
   three buttons above it. */
.report-screen__export-help{max-width:52ch;margin:0;
  font-size:var(--fs-caption);line-height:1.5;color:var(--ink-secondary);text-align:right}
@media (max-width: 820px){
  .report-screen__actions{align-items:stretch}
  .report-screen__export-help{text-align:left;max-width:none}
}
/* iPad portrait and narrower: the bar stacks, and the actions take the full
   width so the two exports stay side by side rather than each spanning the
   screen and reading as two unrelated primary choices. */
@media (max-width: 820px){
  .report-screen__bar{flex-direction:column;align-items:stretch}
  .report-screen__action-row{gap:var(--s-2)}
}
/* V252 release polish. On paper the report IS the document — it gets filed
   in a record, handed round an IEP meeting, or attached to a funding packet.
   The action bar is the way to GET that document, not part of it, so a
   printed page that opens with "Back to Report Center / Print / Save PDF /
   Excel workbook / Data export (CSV)" and a paragraph explaining which
   export is which reads as a screenshot of software rather than a clinical
   record.

   One rule, on the container. Every control the owner named lives inside
   .report-screen__bar — the Back button, the action row, both exports and
   .report-screen__export-help — so hiding the container is the whole fix;
   naming each child would be five chances to miss one when the bar next
   grows a button. The error-state bar (bd_reports.js, the "could not be
   generated" screen) carries the same class and is correctly suppressed too.

   !important, and this is the reason rather than habit: .report-screen__bar
   sets display:flex at the same (0,1,0) specificity, so only source order
   separates them. surfaces.css loads after admin.css in app/admin.html, but
   in the page layer this sheet is bundled by ui/main.js where that ordering
   is the bundler's to decide. The declaration should not depend on it.

   Screen presentation is untouched, and so is every byte of report content:
   this rule sets one property, on one container, inside print media only. */
@media print{
  .report-screen__bar{display:none!important}
}
.report-screen__title{font-family:var(--font-brand);font-size:var(--fs-title-3);
  font-weight:var(--fw-bold);color:var(--ink-primary);line-height:var(--lh-tight);
  margin:0 0 var(--s-1)}
/* The title takes focus when a report renders, so it must not paint a ring
   the way a clicked control does - it is a landing point, not a target. */
.report-screen__title:focus{outline:none}
.report-screen__title:focus-visible{outline:2px solid var(--focus-ring);outline-offset:3px}
.report-screen__meta{font-size:var(--fs-caption);color:var(--ink-secondary);
  margin:0 0 var(--s-5)}

/* ═══════════════════════════════════════════════════════════════════════
   Staff Time
   Payroll review: the numbers are the point, so they get the weight and
   everything around them gets out of the way.
   ═══════════════════════════════════════════════════════════════════════ */
#tab-hours .ts-page-head h2{font-family:var(--font-brand);font-size:var(--fs-title-2);
  font-weight:var(--fw-bold);color:var(--ink-primary);letter-spacing:var(--tracking-display);
  line-height:var(--lh-tight);margin:0 0 var(--s-1)}
#tab-hours .ts-page-head .page-sub{font-size:var(--fs-footnote);color:var(--ink-secondary);
  line-height:var(--lh-body);margin:0;max-width:72ch}
#tab-hours .ts-summary>div,#tab-hours .ts-totals>div{
  background:var(--bg-sunken);border:1px solid var(--line-subtle);border-radius:var(--r-lg)}
#tab-hours .ts-summary span,#tab-hours .ts-totals span{
  font-size:var(--fs-micro);letter-spacing:var(--tracking-micro);text-transform:uppercase;
  color:var(--ink-secondary);font-weight:var(--fw-bold)}
/* Tabular figures so a column of hours lines up digit-for-digit - a reviewer
   scanning for the odd number should not have to read each one. */
#tab-hours .ts-summary b,#tab-hours .ts-totals b{font-family:var(--font-ui);
  font-variant-numeric:tabular-nums;color:var(--ink-primary)}
#tab-hours .ts-summary b{font-size:var(--fs-title-3);font-weight:var(--fw-bold)}
#tab-hours .ts-totals b{font-size:var(--fs-headline);font-weight:var(--fw-semi)}
#tab-hours .ts-review-card{border:1px solid var(--line-default);border-radius:var(--r-lg);
  background:var(--bg-raised);box-shadow:var(--elev-1);padding:var(--s-4);margin-top:var(--s-3)}
#tab-hours .ts-review-head h3{font-size:var(--fs-headline);font-weight:var(--fw-semi);
  color:var(--ink-primary);line-height:var(--lh-tight)}
#tab-hours .ts-review-head .muted{font-size:var(--fs-caption);color:var(--ink-secondary)}
#tab-hours .ts-alert{display:flex;gap:var(--s-2);align-items:flex-start;
  font-size:var(--fs-caption);font-weight:var(--fw-regular);line-height:var(--lh-body);
  border-radius:var(--r-md);margin-top:var(--s-3)}
#tab-hours .ts-alert .bi{flex:0 0 auto;margin-top:1px}
#tab-hours .ts-alert b{font-weight:var(--fw-bold)}
/* display:flex on a <summary> removes the native disclosure marker, so it has
   to be replaced or the row stops reading as expandable at all. */
#tab-hours .ts-days>summary,#tab-hours .ts-legacy-summary{
  cursor:pointer;min-height:var(--target-dense);display:flex;align-items:center;
  gap:var(--s-2);list-style:none}
#tab-hours .ts-days>summary::-webkit-details-marker,
#tab-hours .ts-legacy-summary::-webkit-details-marker{display:none}
#tab-hours .ts-days>summary::before,#tab-hours .ts-legacy-summary::before{
  content:"";width:6px;height:6px;flex:0 0 auto;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:rotate(-45deg);margin-left:2px;
  transition:transform var(--dur-quick) var(--ease-snappy)}
#tab-hours details[open]>summary::before{transform:rotate(45deg)}
#tab-hours .ts-days>summary{font-size:var(--fs-caption);font-weight:var(--fw-semi);
  color:var(--accent-primary)}
#tab-hours .ts-legacy-summary{font-weight:var(--fw-semi);
  font-size:var(--fs-footnote);color:var(--ink-primary)}
#tab-hours .ts-table-wrap td,#tab-hours .ts-table-wrap th{
  padding:var(--s-2) var(--s-3);font-size:var(--fs-caption)}
#tab-hours .ts-table-wrap th{font-size:var(--fs-micro);letter-spacing:var(--tracking-micro);
  text-transform:uppercase;color:var(--ink-tertiary);font-weight:var(--fw-bold)}
#tab-hours .ts-table-wrap td{font-variant-numeric:tabular-nums;color:var(--ink-primary)}
/* The instruction lives in a real label now, not in a placeholder that
   disappears the moment the reviewer starts typing. */
.ts-comment-field{display:flex;flex-direction:column;gap:var(--s-1);
  font-size:var(--fs-caption);font-weight:var(--fw-semi);color:var(--ink-primary)}
.ts-comment-field .muted{font-weight:var(--fw-regular);color:var(--ink-secondary);
  font-size:var(--fs-micro)}
.ts-comment-field textarea{font-family:var(--font-ui);font-size:var(--fs-footnote);
  color:var(--ink-primary);background:var(--bg-raised);
  border:1px solid var(--line-default);border-radius:var(--r-md);
  padding:var(--s-2) var(--s-3);line-height:var(--lh-body);
  min-height:64px;resize:vertical;box-sizing:border-box;width:100%}
.ts-comment-field textarea:focus{border-color:var(--accent-primary);
  box-shadow:var(--focus-glow);outline:none}
.ts-actions__buttons{display:flex;gap:var(--s-2);justify-content:flex-end}
#tab-hours .bd-btn[aria-busy="true"]{cursor:progress}
@media(max-width:600px){.ts-actions__buttons{justify-content:stretch}
  .ts-actions__buttons .bd-btn{flex:1}}

/* ═══════════════════════════════════════════════════════════════════════
   Schedule
   The calendar itself is a vendor widget (EventCalendar 5.12.0) whose
   sizing contract is guarded by 110 cross-view assertions. Nothing here
   touches geometry — only the BehaviorDash chrome wrapped around it and
   the failure path, which was the one plainly broken part of this surface.
   ═══════════════════════════════════════════════════════════════════════ */

/* The "Add" button was #FF7A22 with white text: 2.61:1, a clear 1.4.3
   failure on the one control that starts every appointment. #C2410C is the
   same orange family at 5.18:1, and the hover step stays above 4.5:1. */
#event-calendar-root .ec-bdAdd:not(:disabled){
  background:var(--action-orange)!important;border-color:var(--action-orange)!important;color:#fff!important}
#event-calendar-root .ec-bdAdd:hover:not(:disabled){
  background:var(--action-orange-hover)!important;border-color:var(--action-orange-hover)!important;color:#fff!important}

#event-calendar-root .ec-button{font-family:var(--font-ui);font-size:var(--fs-caption);
  min-height:var(--target-dense)}
#event-calendar-root .ec-title{font-family:var(--font-brand);
  font-size:var(--fs-title-3);font-weight:var(--fw-bold);color:var(--ink-primary);
  letter-spacing:var(--tracking-display)}

/* The old failure state was a flex child of a full-height stage, so its two
   rows spread across ~1100px with the message floating in the middle — it
   read as a rendering bug rather than a message, and it was a dead end with
   no way back. It is a normal card now, with a retry. */
/* v249 friction pass (found by driving the Schedule tab with the calendar
   library unavailable, not by a scanner). This card used to carry
   `border-left:3px solid var(--warning)` — a coloured edge sliver whose
   only job was to indicate the failure state. That is exactly the
   treatment CLAUDE.md prohibits outright: "no green (or any colour)
   sliver, chip, bracket, or edge marker clinging to the corner or side of
   a shape to indicate state."
   The rule's own stated remedy is applied instead — strengthen the FILL.
   The card now sits on --warning-soft, keeps its full 1px border on all
   four sides, and keeps the --warning-inked icon it already had, so the
   state still reads instantly without a decorative marker on one edge. */
.sched-failure{display:flex;gap:var(--s-3);align-items:flex-start;align-self:start;
  margin:var(--s-5);padding:var(--s-5);max-width:52ch;
  background:var(--warning-soft);border:1px solid var(--line-default);
  border-radius:var(--r-lg);box-shadow:var(--elev-1)}
.sched-failure>.bi{font-size:20px;line-height:1;color:var(--warning);flex:0 0 auto;margin-top:1px}
.sched-failure__body{display:flex;flex-direction:column;gap:var(--s-2);align-items:flex-start;min-width:0}
.sched-failure__body b{font-size:var(--fs-headline);font-weight:var(--fw-semi);color:var(--ink-primary)}
.sched-failure__body p{margin:0;font-size:var(--fs-footnote);color:var(--ink-secondary);
  line-height:var(--lh-body)}
/* The scheduler module paints its own message inside the flex root when the
   library is missing. renderSchedule() replaces it, but if it is ever shown
   it must not stretch either. */
.ec-evaluation-engine-error{align-self:start;align-content:start;max-width:52ch}

/* ═══════════════════════════════════════════════════════════════════════
   Programs
   A catalog of 615 clinical templates. The job of this surface is to get a
   BCBA from "I need something for transitions" to the right card — so the
   search, the filters and the result count are the load-bearing parts, and
   everything else recedes.
   ═══════════════════════════════════════════════════════════════════════ */
#tab-programs .program-library-head h2{font-family:var(--font-brand);
  font-size:var(--fs-title-2);font-weight:var(--fw-bold);color:var(--ink-primary);
  letter-spacing:var(--tracking-display);line-height:var(--lh-tight);margin:0 0 var(--s-1)}
#tab-programs .program-library-head p{font-size:var(--fs-footnote);color:var(--ink-secondary);
  line-height:var(--lh-body);margin:0;max-width:76ch}
#tab-programs .program-library-count{font-size:var(--fs-caption);color:var(--ink-secondary);
  font-weight:var(--fw-semi);font-variant-numeric:tabular-nums;white-space:nowrap}

/* Library tabs: the selected one already reads as selected visually.
   aria-pressed carries the same fact to a screen reader, and the inactive
   label gets enough contrast to be read rather than guessed at. */
#tab-programs .program-library-tabs button{font-size:var(--fs-caption)!important;
  font-weight:var(--fw-semi);color:var(--ink-secondary)!important;
  min-height:var(--target-dense);
  transition:background var(--dur-quick) var(--ease-snappy),
             color var(--dur-quick) var(--ease-snappy)}
#tab-programs .program-library-tabs button.active{color:var(--accent-primary)!important}
#tab-programs .program-library-tabs button b{font-variant-numeric:tabular-nums}

#tab-programs .program-searchbox input{min-height:var(--target-touch);
  font-family:var(--font-ui);font-size:var(--fs-footnote);color:var(--ink-primary);
  border-radius:var(--r-md);
  transition:border-color var(--dur-quick) var(--ease-snappy),
             box-shadow var(--dur-quick) var(--ease-snappy)}
#tab-programs .program-searchbox input:hover{border-color:var(--line-strong)}
#tab-programs .program-searchbox input:focus{border-color:var(--accent-primary);
  box-shadow:var(--focus-glow);outline:none}
#tab-programs .program-searchbox input::placeholder{color:var(--ink-tertiary)}
#tab-programs .program-filterbar select{min-height:var(--target-touch);
  font-family:var(--font-ui);font-size:var(--fs-footnote);color:var(--ink-primary)}

#tab-programs .program-measurement-coverage-grid>button{
  min-height:var(--target-touch);cursor:pointer;
  transition:border-color var(--dur-quick) var(--ease-snappy),
             background var(--dur-quick) var(--ease-snappy),
             transform var(--dur-quick) var(--ease-snappy)}
#tab-programs .program-measurement-coverage-grid>button:active{transform:scale(var(--lift-press))}
#tab-programs .program-measurement-coverage-copy b{font-size:var(--fs-footnote);
  font-weight:var(--fw-semi);color:var(--ink-primary)}
#tab-programs .program-measurement-coverage-copy small{font-size:var(--fs-caption);
  color:var(--ink-secondary);line-height:var(--lh-body)}
#tab-programs .program-measurement-coverage-grid>button em{font-style:normal;
  font-variant-numeric:tabular-nums;font-weight:var(--fw-bold)}

#tab-programs .program-card h3{font-family:var(--font-ui);font-size:var(--fs-headline);
  font-weight:var(--fw-semi);color:var(--ink-primary);line-height:var(--lh-tight)}
#tab-programs .program-card p{font-size:var(--fs-caption);color:var(--ink-secondary);
  line-height:var(--lh-body)}
#tab-programs .program-card{transition:border-color var(--dur-quick) var(--ease-snappy),
             box-shadow var(--dur-quick) var(--ease-snappy)}
#tab-programs .program-card:hover{border-color:var(--line-strong);box-shadow:var(--elev-2)}
#tab-programs .program-empty b{font-size:var(--fs-headline);color:var(--ink-primary)}
#tab-programs .program-empty{color:var(--ink-secondary);font-size:var(--fs-footnote)}

/* The request-changes note is a real labelled field now, not a placeholder. */
.program-review-note-field{display:flex;flex-direction:column;gap:var(--s-1)}
.program-review-note-field label{display:flex;flex-direction:column;gap:2px;
  font-size:var(--fs-caption);font-weight:var(--fw-semi);color:var(--ink-primary)}
.program-review-note-field label .muted{font-weight:var(--fw-regular);
  color:var(--ink-secondary);font-size:var(--fs-micro)}
.program-review-note-field textarea{font-family:var(--font-ui);font-size:var(--fs-footnote);
  color:var(--ink-primary);background:var(--bg-raised);border:1px solid var(--line-default);
  border-radius:var(--r-md);padding:var(--s-2) var(--s-3);line-height:var(--lh-body);
  min-height:96px;resize:vertical;box-sizing:border-box;width:100%}
.program-review-note-field textarea:focus{border-color:var(--accent-primary);
  box-shadow:var(--focus-glow);outline:none}
.program-review-note-field .msg:empty{display:none}

/* ═══════════════════════════════════════════════════════════════════════
   Sign-in
   The first screen every user meets, and the one surface this rebuild had
   not touched. Both fields were placeholder-only, so both had an empty
   accessible name.
   ═══════════════════════════════════════════════════════════════════════ */
.login-field{display:flex;flex-direction:column;gap:var(--s-1);
  font-family:var(--font-ui);font-size:var(--fs-caption);font-weight:var(--fw-semi);
  color:var(--ink-secondary);margin-bottom:var(--s-3);text-align:left}
.login-field input{width:100%;box-sizing:border-box;min-height:var(--target-touch);
  font-family:var(--font-ui);font-size:var(--fs-body);color:var(--ink-primary);
  padding:0 var(--s-3);border:1px solid var(--line-default);border-radius:var(--r-md);
  background:var(--bg-raised);
  transition:border-color var(--dur-quick) var(--ease-snappy),
             box-shadow var(--dur-quick) var(--ease-snappy)}
.login-field input:hover{border-color:var(--line-strong)}
.login-field input:focus{border-color:var(--accent-primary);box-shadow:var(--focus-glow);outline:none}
.login-field input[aria-invalid="true"]{border-color:var(--danger)}
#login .msg:empty{display:none}

/* A field that failed validation says so on the field itself, not only in a
   message elsewhere on the page. Colour alone would fail 1.4.1, so the border
   change rides along with aria-invalid and a described-by message. */
#tab-reports [aria-invalid="true"],
#tab-programs [aria-invalid="true"],
#tab-hours [aria-invalid="true"]{border-color:var(--danger)!important}

/* Staff Time's table headers were 700 where all five other tables are 600. */
#tab-hours .ts-table-wrap th{font-weight:var(--fw-semi)}

/* ═══════════════════════════════════════════════════════════════════════
   Report templates as destinations
   IEP Review used to be an <option> in a "Start from" select, 738px down
   the page and three levels in. It is a named report a BCBA arrives
   intending to run, so it sits on the home screen beside the reports it is
   a sibling of, and opening one enters its own screen.
   ═══════════════════════════════════════════════════════════════════════ */
.report-tpl-note{font-size:var(--fs-caption);color:var(--ink-secondary);
  line-height:var(--lh-body);margin:calc(var(--s-2) * -1) 0 var(--s-3);max-width:70ch}

/* A template card is a report card wearing one difference: it opens an
   editable draft rather than generating straight away. The dashed leading
   edge says "starting point" without a decorative accent chip. */
.report-pick--tpl{border-style:dashed}
.report-pick--tpl:hover:not(:disabled){border-style:solid}
.report-pick__mine{background:var(--accent-soft);color:var(--accent-on-soft);
  font-size:var(--fs-micro);font-weight:var(--fw-bold);border-radius:var(--r-pill);
  padding:2px var(--s-2);margin-left:var(--s-2);white-space:nowrap}

.report-blank-link{display:inline-flex;align-items:center;gap:var(--s-1);
  margin-top:var(--s-4);padding:var(--s-2) 0;background:none;border:0;
  font-family:var(--font-ui);font-size:var(--fs-footnote);font-weight:var(--fw-semi);
  color:var(--accent-primary);cursor:pointer;min-height:var(--target-dense)}
.report-blank-link:hover:not(:disabled){background:none;text-decoration:underline}

/* ── The builder, as a screen ─────────────────────────────────────────── */
.bld-bar{display:flex;justify-content:flex-start;margin-bottom:var(--s-4)}
.bld-eyebrow{font-size:var(--fs-micro);letter-spacing:var(--tracking-micro);
  text-transform:uppercase;color:var(--ink-tertiary);font-weight:var(--fw-bold);
  margin:0 0 var(--s-1)}
.bld-title{font-family:var(--font-brand);font-size:var(--fs-title-2);
  font-weight:var(--fw-bold);color:var(--ink-primary);letter-spacing:var(--tracking-display);
  line-height:var(--lh-tight);margin:0 0 var(--s-1)}
/* The title takes focus on arrival, so it must not paint a ring like a
   clicked control — it is a landing point, not a target. */
.bld-title:focus{outline:none}
.bld-title:focus-visible{outline:2px solid var(--focus-ring);outline-offset:3px}
.bld-sub{font-size:var(--fs-footnote);color:var(--ink-secondary);
  line-height:var(--lh-body);margin:0 0 var(--s-5);max-width:70ch}
#custom-builder .rpt-field{display:flex;flex-direction:column;gap:var(--s-1);
  font-size:var(--fs-caption);font-weight:var(--fw-semi);color:var(--ink-secondary)}
#custom-builder .rpt-field select{min-height:var(--target-touch);
  font-family:var(--font-ui);font-size:var(--fs-footnote);color:var(--ink-primary)}

/* OWNER PRODUCT REVIEW, finding 4. Generating the report and saving the
   settings used to share one flat row. They are separated here by job, not
   decorated: the generate action stands alone with nothing competing for
   the same line, and the template group is a quiet recessed panel so it
   reads as "this is about templates", not as a second way to run a report.
   No corner accent, no edge marker — a fill and a gap. */
.bld-actions{display:flex;flex-wrap:wrap;gap:var(--s-2);margin-top:var(--s-5)}
.bld-template-save{margin-top:var(--s-5);padding:var(--s-4);
  background:var(--surface-sunken,#F5F7FA);border-radius:var(--radius-md,10px)}
.bld-template-save__label{font-size:var(--fs-footnote);font-weight:var(--fw-bold);
  color:var(--ink-primary);margin-bottom:2px}
.bld-template-save__note{font-size:var(--fs-caption);color:var(--ink-secondary);
  line-height:var(--lh-body);margin:0 0 var(--s-3);max-width:60ch}
/* align-items:center rather than the default stretch: the Save button used
   to sit a few pixels below the input's baseline because the row let each
   child size itself. */
.bld-template-save__row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s-2)}
.bld-template-save__row input{flex:1 1 240px;min-width:0;
  min-height:var(--target-touch);font-family:var(--font-ui);
  font-size:var(--fs-footnote);color:var(--ink-primary)}
/* iPad portrait and narrower: the name field takes the full width and the
   two buttons sit under it, rather than the field collapsing to a sliver
   between them. */
@media (max-width: 820px){
  .bld-template-save__row input{flex:1 1 100%}
}

/* ═══════════════════════════════════════════════════════════════════════
   Behavior Plan builder
   Measured before: 14 distinct font sizes on one screen with a 7.8px floor,
   the builder rendering below the entire student roster, and the version
   number reaching the screen as "vundefined". The screen model and the
   version guard are in admin.html; this is the type and density pass.
   ═══════════════════════════════════════════════════════════════════════ */
#plan-panel .plan-page-head>div>span,
#beh-panel .plan-page-head>div>span{font-size:var(--fs-micro);
  letter-spacing:var(--tracking-micro);text-transform:uppercase;
  color:var(--ink-tertiary);font-weight:var(--fw-bold)}
#plan-panel .plan-page-head h2{font-family:var(--font-brand);
  font-size:var(--fs-title-2);font-weight:var(--fw-bold);color:var(--ink-primary);
  letter-spacing:var(--tracking-display);line-height:var(--lh-tight);margin:0 0 var(--s-1)}
#plan-panel .plan-page-head p{font-size:var(--fs-footnote);color:var(--ink-secondary);
  line-height:var(--lh-body);margin:0;max-width:70ch}

/* The step heading was an <h1> nested under two <h2>s — a level jump
   backwards. It reads as the section heading it is. */
#plan-panel .plan-screen-heading h1,
#plan-panel .plan-phase>h1{font-family:var(--font-brand);font-size:var(--fs-title-3);
  font-weight:var(--fw-bold);color:var(--ink-primary);line-height:var(--lh-tight);
  letter-spacing:var(--tracking-display);margin:0 0 var(--s-1)}

/* Nothing on this screen goes below the 11px floor. The rail's step
   descriptions and the summary tiles were the worst offenders at 7.8-9px. */
#plan-panel .plan-builder-sidebar small,
#plan-panel .plan-screen-nav small,
#plan-panel .plan-builder-sidebar-help small,
#plan-panel .plan-builder-sidebar-head small,
#plan-panel .plan-version-manager small{font-size:var(--fs-caption);
  color:var(--ink-secondary);line-height:var(--lh-body)}
#plan-panel .plan-builder-sidebar-head span,
#plan-panel .plan-builder-summary span,
#plan-panel .plan-review-card span{font-size:var(--fs-micro);
  letter-spacing:var(--tracking-micro);text-transform:uppercase;
  color:var(--ink-tertiary);font-weight:var(--fw-bold)}
#plan-panel .plan-builder-summary b,
#plan-panel .plan-review-card b{font-family:var(--font-ui);
  font-variant-numeric:tabular-nums;font-size:var(--fs-title-3);
  font-weight:var(--fw-bold);color:var(--ink-primary)}
#plan-panel .plan-screen-nav b{font-size:var(--fs-footnote);
  font-weight:var(--fw-semi);color:var(--ink-primary)}

/* Every field on the step gets the same label treatment and target height,
   instead of the inline 12px/var(--muted) pattern repeated per field. */
#plan-panel label{font-size:var(--fs-caption);font-weight:var(--fw-semi);
  color:var(--ink-secondary)}
#plan-panel input:not([type=checkbox]):not([type=radio]),
#plan-panel select,
#plan-panel textarea{font-family:var(--font-ui);font-size:var(--fs-footnote);
  color:var(--ink-primary);min-height:var(--target-touch)}
#plan-panel textarea{min-height:88px}

/* The three rules that actually set sub-11px text on this screen. Targeted
   by their own class names because guessing at descendant selectors is how
   the previous pass lost: .plan-at-glance span at 7.8px, the step numeral
   at 9.5px, the version note at 10.5px. */
#plan-panel .plan-at-glance span{font-size:var(--fs-micro);color:var(--ink-tertiary);
  letter-spacing:var(--tracking-micro);font-weight:var(--fw-bold)}
#plan-panel .plan-at-glance b{font-family:var(--font-ui);font-size:var(--fs-footnote);
  font-variant-numeric:tabular-nums;color:var(--ink-primary)}
/* The step badge shipped #667e91 on #edf2f6 — 3.76:1, under the 4.5:1 floor
   for the number that tells you which step you are on. --ink-secondary
   measures 6.50:1 on the same fill. The active and done states set their own
   ink and are unaffected. */
#plan-panel .plan-screen-step-number{font-size:var(--fs-caption);font-weight:var(--fw-bold);
  width:28px;height:28px}
/* Scoped to the RESTING badge only. An unscoped `#plan-panel .plan-...` is
   (1,1,0) and outranks the active state's own (0,3,0) white-on-navy rule —
   the first attempt at this fixed 3.76:1 and broke the active step to
   1.48:1. The active and done states already set their own ink. */
#plan-panel .plan-screen-nav button:not(.active):not(.done) .plan-screen-step-number{
  color:var(--ink-secondary)}
#plan-panel .plan-version-note{font-size:var(--fs-caption);color:var(--ink-secondary);
  line-height:var(--lh-body)}
#plan-panel .plan-empty-state b{font-size:var(--fs-footnote)}
#plan-panel .plan-empty-state span{font-size:var(--fs-caption)}

/* The last five. Enumerated from the rendered screen rather than guessed at,
   which is the only reason this pass finished — the previous two rounds both
   wrote selectors that matched nothing. */
#plan-panel .plan-screen-heading>span{font-size:var(--fs-micro);
  letter-spacing:var(--tracking-micro);text-transform:uppercase;
  color:var(--ink-tertiary);font-weight:var(--fw-bold)}
#plan-panel .plan-builder-sidebar-help b{font-size:var(--fs-caption);
  font-weight:var(--fw-semi);color:var(--ink-primary)}
#plan-panel .plan-inline-save .msg{font-size:var(--fs-caption)}
/* The primary action on every step was 10px with an 8.5px sub-label — the
   smallest text on screen sat inside the button you are meant to press. */
#plan-panel .plan-next-button{font-size:var(--fs-footnote);
  min-height:var(--target-touch);font-weight:var(--fw-semi)}
#plan-panel .plan-next-label{font-size:var(--fs-caption);font-weight:var(--fw-regular)}

/* ═══════════════════════════════════════════════════════════════════════
   Program store — the Program library, inside the plan builder
   The library holds every source-backed Program, but reaching it used to
   mean leaving the plan, filling the Programs-tab basket, and coming back.
   This is that same pool as a search sheet on step 2 of the builder.

   Two rules govern every hover here (see CLAUDE.md): admin.css ships
   `button:hover:not(:disabled){background:var(--brand-2)}` at (0,2,1), so
   every hover rule below carries `:not(:disabled)` to out-specify it AND
   declares `background` explicitly — a specificity tie does nothing for a
   property the rule never sets. Selection is carried by fill, ink and
   weight. No corner brackets, no edge slivers, ever.
   ═══════════════════════════════════════════════════════════════════════ */
/* Reported on Safari: the sheet collapsed to head + footer, with the search
   field and the library toggle scrolled out of view ABOVE a single visible
   result row and a scrollbar at the dialog's edge. That symptom means the
   SHEET BODY was scrolling — it never should. Two nested scrollers
   (.bd-sheet__body and .plan-store-results) were negotiating height with a
   <dialog> that sizes to fit-content, and the outer one won.

   Chromium resolved that negotiation the other way, which is why every test
   here passed and it shipped anyway. Rather than tune a mechanism that
   behaves differently per engine, the negotiation is removed:

     * the picker DECLARES its height, so the body always has real content
       to size to instead of inferring it from an overflow:auto child;
     * the body is told not to scroll for this sheet (bd-sheet__body--flush,
       applied in admin.html), so it cannot become the outer scroller;
     * the controls and the count row are flex:none, so they can never be
       shrunk away to make room for the list.

   Do not put this back to min-height:0 plus an inner max-height. */
/* One definite height at the top, distributed downwards. Nothing here
   infers a height from its own contents, so no engine gets to resolve that
   differently — which is the whole bug.

     .bd-sheet--tall      definite height
       .bd-sheet__body--flush   flex:1, min-height:0 -> definite
         .plan-store            flex:1, min-height:0 -> definite
           .plan-store-results  flex:1, min-height:0 -> scrolls

   Two earlier attempts subtracted the sheet's chrome as a constant. The
   header wraps to more lines on a narrow screen, so any constant is wrong
   somewhere: 240px fit a laptop and overflowed a 390px phone, and CI caught
   it. Measuring the chrome is not needed if the body simply takes what is
   left. */
.bd-sheet--tall{height:min(84vh,760px)}
@media (max-height:600px){ .bd-sheet--tall{height:calc(100vh - var(--s-4))} }
.bd-sheet__body--flush{overflow:hidden;display:flex;flex-direction:column;min-height:0}
.plan-store{display:flex;flex-direction:column;gap:var(--s-3);flex:1 1 auto;min-height:0}

.plan-store-controls{display:flex;flex-wrap:wrap;gap:var(--s-3);align-items:flex-end;flex:none}
.plan-store-control{display:flex;flex-direction:column;gap:var(--s-1);min-width:0}
.plan-store-control:first-child{flex:1 1 240px}
/* A visible label, not a visually-hidden one. The placeholder is gone the
   moment you type, and then nothing on screen says what the field is. */
.plan-store-label{font-size:var(--fs-caption);font-weight:var(--fw-semi);
  color:var(--ink-secondary);line-height:1.2}
.plan-store-search{position:relative;display:flex;align-items:center;margin:0}
.plan-store-search>.bi{position:absolute;left:var(--s-3);color:var(--ink-tertiary);
  font-size:var(--fs-footnote);pointer-events:none}
.plan-store .plan-store-search input{width:100%;box-sizing:border-box;min-height:44px;
  padding:var(--s-2) var(--s-3) var(--s-2) 36px;font-size:var(--fs-body);
  font-family:var(--font-ui);color:var(--ink-primary);background:var(--bg-raised);
  border:1px solid var(--line-default);border-radius:var(--r-md)}
.plan-store .plan-store-search input:focus-visible{outline:2px solid var(--accent-primary);
  outline-offset:1px;box-shadow:var(--focus-glow)}

.plan-store-libs{display:flex;gap:2px;padding:2px;background:var(--bg-sunken);
  border-radius:var(--r-md)}
.plan-store .plan-store-libs button{min-height:40px;padding:0 var(--s-3);border:0;
  background:transparent;color:var(--ink-secondary);font-size:var(--fs-caption);
  font-weight:var(--fw-semi);border-radius:8px;cursor:pointer}
.plan-store .plan-store-libs button:hover:not(:disabled){background:var(--bg-raised);
  color:var(--ink-primary)}
/* The selected library is the filled one. Fill and ink, nothing clipped to
   an edge. */
.plan-store .plan-store-libs button[aria-pressed="true"]{background:var(--bg-raised);
  color:var(--ink-primary);font-weight:var(--fw-bold);
  box-shadow:0 1px 2px rgba(16,36,62,.10)}
.plan-store .plan-store-libs button[aria-pressed="true"]:hover:not(:disabled){
  background:var(--bg-raised);color:var(--ink-primary)}

/* What you are looking at and what you have taken, on one row. These were
   two stacked lines of small text above the list — the same stacking that
   makes the rest of this app read as cluttered. */
.plan-store-meta{display:flex;align-items:baseline;justify-content:space-between;
  gap:var(--s-3);flex-wrap:wrap;margin:0;flex:none}
.plan-store-note{margin:0;font-size:var(--fs-caption);color:var(--ink-tertiary)}
.plan-store-count{margin:0;font-size:var(--fs-caption);font-weight:var(--fw-semi);
  color:var(--ink-secondary)}

/* The single scroller, filling whatever the picker's declared height leaves
   after the controls and the count row. No vh cap of its own — two
   competing height sources is what made this fragile. */
.plan-store-results{flex:1 1 auto;min-height:0;overflow-y:auto;
  padding-right:var(--s-1)}
.plan-store-list{display:flex;flex-direction:column;gap:var(--s-2)}

.plan-store-item{background:var(--bg-raised);border:1px solid var(--line-subtle);
  border-radius:var(--r-md);overflow:hidden}
/* A Program with picks in it reads as selected through a stronger fill and
   a full border — never a coloured corner. */
.plan-store-item.has-picks{background:var(--accent-soft);
  border-color:var(--line-default)}
/* admin.css sets button{color:#fff} at (0,0,1). Every string inside these
   controls sets its own ink today, so nothing is white-on-white — but that
   is luck, not design: one bare text node added later would be invisible.
   The ink is declared on the button itself, hover included. */
.plan-store .plan-store-item-head{display:flex;width:100%;align-items:center;
  gap:var(--s-3);padding:var(--s-3);border:0;background:transparent;
  color:var(--ink-primary);text-align:left;cursor:pointer;min-height:52px}
.plan-store .plan-store-item-head:hover:not(:disabled){background:var(--bg-sunken);
  color:var(--ink-primary)}
.plan-store .plan-store-item.has-picks .plan-store-item-head:hover:not(:disabled){
  background:var(--accent-soft);color:var(--ink-primary)}
.plan-store .plan-store-item-head:focus-visible{outline:2px solid var(--accent-primary);
  outline-offset:-2px}
.plan-store-item-copy{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.plan-store-item-copy b{font-size:var(--fs-footnote);font-weight:var(--fw-semi);
  color:var(--ink-primary);line-height:var(--lh-snug)}
.plan-store-item-copy small{font-size:var(--fs-caption);color:var(--ink-secondary)}
.plan-store .plan-store-item-head>.bi{color:var(--ink-tertiary);
  font-size:var(--fs-footnote);flex:none}

.plan-store-item-body{padding:0 var(--s-3) var(--s-3);
  border-top:1px solid var(--line-subtle)}
.plan-store-item-desc{margin:var(--s-3) 0 var(--s-2);font-size:var(--fs-caption);
  color:var(--ink-secondary);line-height:var(--lh-body);max-width:70ch;
  overflow-wrap:anywhere}
.plan-store-item-copy b,.plan-store-target-copy b,
.plan-store-target-copy small{overflow-wrap:anywhere}
.plan-store .plan-store-all{min-height:36px;padding:0 var(--s-3);margin:0 0 var(--s-2);
  font-size:var(--fs-caption);font-weight:var(--fw-semi);color:var(--accent-on-soft);
  background:var(--accent-soft);border:1px solid var(--line-subtle);
  border-radius:var(--r-pill);cursor:pointer}
.plan-store .plan-store-all:hover:not(:disabled){background:var(--accent-soft);
  color:var(--accent-on-soft);border-color:var(--line-strong)}

.plan-store-targets{list-style:none;margin:0;padding:0;display:flex;
  flex-direction:column;gap:var(--s-1)}
.plan-store .plan-store-target{display:flex;align-items:flex-start;gap:var(--s-3);
  padding:var(--s-2) var(--s-3);min-height:44px;border-radius:var(--r-sm);
  background:var(--bg-inset);cursor:pointer;font-weight:var(--fw-regular)}
.plan-store .plan-store-target:hover{background:var(--bg-sunken)}
/* Picked: filled and bolder, the way a selected row reads in a native list. */
.plan-store .plan-store-target.picked{background:var(--accent-soft)}
.plan-store .plan-store-target.picked .plan-store-target-copy b{
  font-weight:var(--fw-bold);color:var(--accent-on-soft)}
.plan-store .plan-store-target input[type="checkbox"]{width:18px;height:18px;
  margin:2px 0 0;flex:none;accent-color:var(--accent-primary);cursor:pointer}
.plan-store-target-copy{display:flex;flex-direction:column;gap:2px;min-width:0}
.plan-store-target-copy b{font-size:var(--fs-footnote);font-weight:var(--fw-semi);
  color:var(--ink-primary);line-height:var(--lh-snug)}
.plan-store-target-copy small{font-size:var(--fs-caption);color:var(--ink-secondary);
  line-height:var(--lh-body)}

/* The entry point on step 2 sits beside the section heading and in the
   empty state, so the library is reachable from both. */
#plan-panel .plan-section-title{display:flex;align-items:flex-start;
  justify-content:space-between;gap:var(--s-3);flex-wrap:wrap}
#plan-panel .plan-store-open{display:inline-flex;align-items:center;gap:var(--s-2);
  min-height:40px;padding:0 var(--s-4);flex:none;
  font-size:var(--fs-caption);font-weight:var(--fw-semi);
  color:var(--ink-on-accent);background:var(--accent-primary);
  border:1px solid var(--accent-primary);border-radius:var(--r-pill);cursor:pointer}
#plan-panel .plan-store-open:hover:not(:disabled){background:var(--accent-hover);
  border-color:var(--accent-hover);color:var(--ink-on-accent)}
#plan-panel .plan-store-open:focus-visible{outline:2px solid var(--accent-primary);
  outline-offset:2px}
#plan-panel .plan-empty-state .plan-store-open{margin-top:var(--s-3)}

@media (max-width:640px){
  /* The controls stack, which turns a row-axis flex basis into a TALL search
     field: the first phone screenshot showed one eating the sheet with the
     result list crushed to a sliver underneath. On the cross axis the field
     sizes to its content. */
  .plan-store-controls{flex-direction:column;align-items:stretch}
  .plan-store-control:first-child{flex:0 0 auto}
  .plan-store-search{flex:0 0 auto}
  .plan-store-libs{width:100%}
  .plan-store .plan-store-libs button{flex:1 1 0;min-width:0}
  /* The sheet already caps its own height, so the list takes what is left
     rather than competing with it for a fixed slice. */
  /* Height comes from the same formula at every size — see .plan-store. */
}

/* ═══════════════════════════════════════════════════════════════════════
   Assignments — the plan, from the screen that says who works with whom
   The table answered "who works with whom" and stopped. Whether that
   student even has a plan, and what is in it, was three navigations away.
   The state pill reads from cache.behaviorPlans (already loaded); the
   preview fetches the plan only when opened, and never writes.

   Hover rules carry :not(:disabled) and declare background — admin.css's
   button:hover:not(:disabled) is (0,2,1). See CLAUDE.md.
   ═══════════════════════════════════════════════════════════════════════ */
#tab-assignments .assign-row-actions{display:inline-flex;gap:var(--s-2);
  justify-content:flex-end;flex-wrap:wrap}

.assign-plan-pill{display:inline-flex;align-items:center;gap:var(--s-1);
  padding:3px var(--s-3);border-radius:var(--r-pill);
  font-size:var(--fs-caption);font-weight:var(--fw-semi);white-space:nowrap}
/* State is carried by fill and ink, never by a marker on one edge, and the
   word is always present — colour is not the only thing saying which is which. */
.assign-plan-pill--active{background:#EAF4E4;color:#33590F}
.assign-plan-pill--draft{background:#FDECE0;color:#8C4313}
.assign-plan-pill--archived{background:var(--bg-sunken);color:var(--ink-secondary)}
.assign-plan-pill--none{background:var(--bg-sunken);color:var(--ink-tertiary)}

/* ── Read-only plan preview (sheet) ────────────────────────────────────── */
/* ═══════════════════════════════════════════════════════════════════════
   Programs — paging, because 615 cards is not a page
   Rendering every match built 19,180 DOM nodes and a 176,888px document.
   On a phone the browser composites 177 metres of page, and scrolling it
   was reported as "very very glitchy, barely scrollable". A page at a time,
   with the remainder stated rather than silently dropped.
   ═══════════════════════════════════════════════════════════════════════ */
#tab-programs .program-more{grid-column:1/-1;display:flex;flex-direction:column;
  align-items:center;gap:var(--s-2);padding:var(--s-5) var(--s-4);
  background:var(--bg-inset);border:1px dashed var(--line-default);
  border-radius:var(--r-lg);text-align:center}
#tab-programs .program-more p{margin:0;font-size:var(--fs-footnote);
  font-weight:var(--fw-semi);color:var(--ink-primary)}
#tab-programs .program-more small{font-size:var(--fs-caption);color:var(--ink-tertiary)}
#tab-programs .program-more button{min-height:var(--target-touch);
  padding:0 var(--s-5);font-size:var(--fs-footnote);font-weight:var(--fw-semi);
  border-radius:var(--r-pill);cursor:pointer;
  background:var(--accent-primary);color:var(--ink-on-accent);
  border:1px solid var(--accent-primary)}
#tab-programs .program-more button:hover:not(:disabled){
  background:var(--accent-hover);border-color:var(--accent-hover);color:var(--ink-on-accent)}
#tab-programs .program-more button.gray{background:var(--bg-raised);
  color:var(--ink-secondary);border-color:var(--line-default)}
#tab-programs .program-more button.gray:hover:not(:disabled){
  background:var(--bg-sunken);color:var(--ink-primary);border-color:var(--line-strong)}
@media (min-width:641px){
  #tab-programs .program-more{flex-direction:row;justify-content:center}
  #tab-programs .program-more small{flex-basis:100%}
}

/* A horizontal strip inside a vertically scrolling page must not hand its
   overscroll to the browser — that is what turns a sideways nudge mid-swipe
   into a back-navigation gesture. */
#tab-programs .program-library-tabs,
#tab-programs .program-category-showcase,
#tab-programs .program-measurement-coverage-grid{overscroll-behavior-x:contain}

/* ═══════════════════════════════════════════════════════════════════════
   Adding a student — names first, code optional
   The roster used to take one field, "student code", and the shipped demo
   data put full names in it. Schools work in names; forcing codes pushes
   staff to keep a paper crosswalk, which is a worse exposure than the
   database and raises wrong-student data entry. Names are first-class now,
   the code is optional, and de-identification moved to the export.
   ═══════════════════════════════════════════════════════════════════════ */
#tab-students .student-add{display:flex;flex-wrap:wrap;align-items:flex-end;
  gap:var(--s-3);margin-top:var(--s-3)}
#tab-students .student-add-fields{display:grid;flex:1 1 420px;
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:var(--s-3)}
#tab-students .student-add label{display:flex;flex-direction:column;gap:var(--s-1);
  font-size:var(--fs-caption);font-weight:var(--fw-semi);color:var(--ink-secondary);
  min-width:0}
/* "optional" is stated, not implied by absence of a required marker. */
#tab-students .student-add label span{font-weight:var(--fw-regular);
  color:var(--ink-tertiary);text-transform:none;letter-spacing:0}
#tab-students .student-add input{width:100%;box-sizing:border-box;
  min-height:var(--target-touch);padding:var(--s-2) var(--s-3);
  font-family:var(--font-ui);font-size:var(--fs-footnote);color:var(--ink-primary);
  background:var(--bg-raised);border:1px solid var(--line-default);
  border-radius:var(--r-md)}
#tab-students .student-add input:focus-visible{outline:2px solid var(--accent-primary);
  outline-offset:1px;box-shadow:var(--focus-glow)}
#tab-students .student-add>button{flex:none;min-height:var(--target-touch);
  padding:0 var(--s-5);border-radius:var(--r-md);cursor:pointer;
  font-size:var(--fs-footnote);font-weight:var(--fw-semi);
  background:var(--accent-primary);color:var(--ink-on-accent);
  border:1px solid var(--accent-primary)}
#tab-students .student-add>button:hover:not(:disabled){background:var(--accent-hover);
  border-color:var(--accent-hover);color:var(--ink-on-accent)}
@media (max-width:640px){
  #tab-students .student-add-fields{grid-template-columns:1fr}
  #tab-students .student-add>button{width:100%}
}
