/* =============================================================================
   Northlink Material 3 control layer
   =============================================================================

   Material 3 (m3.material.io) applied to the controls, using the Northlink
   brand palette. Loaded LAST in index.html so it wins on source order against
   the vendored Bootstrap 3 / AdminLTE sheets without needing inflated
   selectors.

   SPECIFICITY IS DELIBERATE. Selectors here match the legacy ones (.btn, not
   button.btn) so the cascade stays predictable. index.html records what
   happened the last time a sheet reached for `button.btn` (0,1,1): it outranked
   Bootstrap's `.btn` (0,1,0) and repainted every button in the application,
   ignoring btn-xs, btn-link and the rest. Where a rule below needs more weight
   it says why.

   WHY A STYLESHEET RATHER THAN A REWRITE. Every control in the app already
   carries a consistent Bootstrap class name — 275 .form-control, 400+ .btn —
   and all 86 dropdowns and 47 date fields are NATIVE elements, not jQuery
   plugins. So one sheet restyles 90 pages and every persona at once. Adopting
   Tailwind literally would mean rewriting markup on all 90 for the same result.

   TWO PLACES THIS DEPARTS FROM SPEC, both on purpose:

   1. Colour. M3 generates a tonal palette from a seed, which would put primary
      at tone 40. Colleen's brand guideline names #000066 as THE primary, so
      primary is pinned to the brand value and the rest of the ramp is built
      around it. M3 structure, Northlink values.

   2. Filled danger buttons use #E60000, not the brand's #FF0000. Pure #FF0000
      against white text is 4.00:1, which FAILS WCAG AA for body text; #E60000
      is 4.81:1 and passes. The pure brand red is kept for text and icons on
      light backgrounds, where it is fine. Worth raising with Colleen.
   ========================================================================== */

/* ----------------------------------------------------------------- tokens -- */
:root {
  /* --------------------------------------------------------------------------
     Colour. M3 ROLES, NORTHLINK VALUES.

     Every value below is a colour from Colleen Cozett's brand sheet or one of
     its published tints (80/60/40/20%). M3 would derive a tonal palette from a
     seed and put primary at tone 40; the guideline names #000066 as THE
     primary, so brand wins and the ramp is the brand's own.

     The sheet's lightest published tint is 20%, which is far too dark for an
     M3 surface role, so the surface containers below continue the same ramp at
     3/5/8/12% — the brand's hue, just lighter than the sheet had reason to
     print.

     Each "on-container" colour was CHOSEN BY MEASURED CONTRAST, not by eye:
     the full-strength brand colour where it reaches 4.5:1 on its tint, black
     where it does not. Status chips otherwise look right and fail an audit.
     -------------------------------------------------------------------------- */
  --md-sys-color-primary: #000066;                    /* Pantone 648C */
  --md-sys-color-on-primary: #FFFFFF;                /* 17.6:1 */
  --md-sys-color-primary-container: #CCCCE0;            /* 20% tint */
  --md-sys-color-on-primary-container: #000066;

  --md-sys-color-secondary-container: #CCCCE0;          /* selections, nav pill */
  --md-sys-color-on-secondary-container: #000066;

  /* Surfaces: the navy ramp continued past the sheet's 20% floor. */
  --md-sys-color-surface: #FFFFFF;
  --md-sys-color-surface-container-lowest: #FFFFFF;
  --md-sys-color-surface-container-low: #F7F7FA;
  --md-sys-color-surface-container: #F2F2F7;
  --md-sys-color-surface-container-high: #EBEBF3;
  --md-sys-color-surface-container-highest: #E0E0ED;
  --md-sys-color-on-surface: #000000;                /* the guideline's body colour */
  --md-sys-color-on-surface-variant: #666666;

  --md-sys-color-outline: #666666;
  --md-sys-color-outline-variant: #CCCCCC;

  /* Warm Red. #FF0000 under white text is 4.00:1 and FAILS AA for body text,
     so FILLED danger surfaces darken to #E60000 (4.81:1). The brand red itself
     is kept for text and icons on light, where it is fine. Raise with Colleen. */
  --md-sys-color-error: #E60000;
  --md-sys-color-error-text: #FF0000;
  --md-sys-color-on-error: #FFFFFF;
  --md-sys-color-error-container: #FFCCCC;              /* 20% tint */
  --md-sys-color-on-error-container: #000000;

  /* Not M3 roles, but the app needs them (btn-success, btn-warning, labels). */
  --nl-success: #006600;                             /* 7.2:1 under white */
  --nl-success-container: #CCE0CC;
  --nl-on-success-container: #006600;
  --nl-warning: #FF9900;                            /* 9.8:1 under BLACK */
  --nl-warning-container: #FFEBCC;
  --nl-on-warning-container: #000000;
  --nl-on-warning: #000000;                          /* white on orange is 2.14:1 — never */

  /* Shape — exact M3 values. */
  --md-sys-shape-corner-none: 0;
  --md-sys-shape-corner-extra-small: 4px;
  --md-sys-shape-corner-small: 8px;
  --md-sys-shape-corner-medium: 12px;
  --md-sys-shape-corner-large: 16px;
  --md-sys-shape-corner-extra-large: 28px;
  --md-sys-shape-corner-full: 9999px;

  /* Elevation — M3 levels 1-3. */
  --md-sys-elevation-1: 0 1px 2px 0 rgba(0,0,0,.30), 0 1px 3px 1px rgba(0,0,0,.15);
  --md-sys-elevation-2: 0 1px 2px 0 rgba(0,0,0,.30), 0 2px 6px 2px rgba(0,0,0,.15);
  --md-sys-elevation-3: 0 1px 3px 0 rgba(0,0,0,.30), 0 4px 8px 3px rgba(0,0,0,.15);

  /* Motion. */
  --md-sys-motion-short: 150ms cubic-bezier(.2, 0, 0, 1);
  --md-sys-motion-medium: 250ms cubic-bezier(.2, 0, 0, 1);

  /* Component sizes. M3 heights, and M3's own DENSITY SCALE (0 to -3) for the
     data-dense surfaces the spec provides it for — that is what keeps a
     90-page admin usable without departing from Material. */
  --nl-btn-h: 40px;          /* button, density 0   */
  --nl-btn-h-sm: 32px;       /* density -2          */
  --nl-btn-h-xs: 28px;       /* density -3          */
  --nl-field-h: 56px;        /* text field          */
  --nl-field-h-sm: 40px;     /* density -2, in tables and filter bars */
  --nl-row-h: 48px;          /* table row           */
}

/* ------------------------------------------------------------- typography -- */
body,
.content-wrapper,
.main-sidebar,
.main-header {
  font-family: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* body-medium */
body { font-size: 14px; line-height: 20px; letter-spacing: .25px; color: var(--md-sys-color-on-surface); }

/* ---------------------------------------------------------------- buttons -- */
/* M3 common buttons: 40dp tall, corner-full, label-large. */
.btn {
  height: var(--nl-btn-h);
  min-height: var(--nl-btn-h);
  padding: 0 24px;
  border: none;
  border-radius: var(--md-sys-shape-corner-full);
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: .1px;           /* label-large */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-shadow: none;
  text-shadow: none;
  transition: background-color var(--md-sys-motion-short),
              box-shadow var(--md-sys-motion-short),
              filter var(--md-sys-motion-short);
}
.btn:focus-visible {
  outline: 3px solid var(--md-sys-color-primary);
  outline-offset: 2px;
}
/* M3 state layers: 8% hover, 12% pressed, expressed as a brightness shift so
   one rule covers every variant instead of eight overlay pseudo-elements. */
.btn:hover           { filter: brightness(.92); }
.btn:active          { filter: brightness(.86); }
.btn.disabled,
.btn[disabled],
fieldset[disabled] .btn {
  background-color: rgba(27,27,33,.12);
  color: rgba(27,27,33,.38);
  border: none;
  box-shadow: none;
  filter: none;
  opacity: 1;                      /* M3 dims by token, not by opacity */
}

/* Filled — the default emphasis for a primary action. */
.btn-primary { background-color: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary); }
.btn-success { background-color: var(--nl-success); color: #fff; }
.btn-danger  { background-color: var(--md-sys-color-error); color: var(--md-sys-color-on-error); }
/* Black, not white: white on #ff9900 is 2.14:1 and fails AA outright.
   Bootstrap and AdminLTE both ship white here, so this is a correction. */
.btn-warning,
.btn-warning:hover,
.btn-warning:focus,
.btn-warning:active { background-color: var(--nl-warning) !important; color: var(--nl-on-warning) !important; }
.btn-info    { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }

/* Outlined — the M3 answer to Bootstrap's "default" button. */
.btn-default {
  background-color: transparent;
  color: var(--md-sys-color-primary);
  border: 1px solid var(--md-sys-color-outline);
}
.btn-default:hover { background-color: rgba(0,0,102,.08); filter: none; }
.btn-default:active { background-color: rgba(0,0,102,.12); filter: none; }

/* Text button. */
.btn-link {
  height: var(--nl-btn-h);
  padding: 0 12px;
  color: var(--md-sys-color-primary);
  background: transparent;
  border: none;
  text-decoration: none;
}
.btn-link:hover { background-color: rgba(0,0,102,.08); text-decoration: none; filter: none; }

/* Density -2 and -3. These carry the action columns in every table, so they
   must stay small or a 20-row register becomes a scrolling chore. */
.btn-sm, .btn-xs { letter-spacing: .5px; }
.btn-sm { height: var(--nl-btn-h-sm); min-height: var(--nl-btn-h-sm); padding: 0 16px; font-size: 13px; }
.btn-xs { height: var(--nl-btn-h-xs); min-height: var(--nl-btn-h-xs); padding: 0 12px; font-size: 12px; }
.btn-lg { height: 48px; min-height: 48px; padding: 0 28px; font-size: 15px; }

/* Segmented-button treatment: a corner-full pill per button looks wrong in a
   group, so the group becomes one pill with square joins. */
.btn-group > .btn { border-radius: 0; }
.btn-group > .btn:first-child { border-radius: var(--md-sys-shape-corner-full) 0 0 var(--md-sys-shape-corner-full); }
.btn-group > .btn:last-child  { border-radius: 0 var(--md-sys-shape-corner-full) var(--md-sys-shape-corner-full) 0; }
.btn-group > .btn:only-child  { border-radius: var(--md-sys-shape-corner-full); }
.btn-group > .btn + .btn { margin-left: 1px; }

/* ------------------------------------------------------------ text fields -- */
/* M3 FILLED text field: 56dp, corner-extra-small on the TOP corners only, and
   an active indicator that thickens from 1dp to 2dp on focus. */
.form-control {
  height: var(--nl-field-h);
  /* Symmetric, so the browser centres the text. M3's filled field reserves the
     top 20px for a label INSIDE the container — this app puts its labels
     outside (all 275 of them), so that reservation left every value sitting
     low and clipped it outright in short containers like the header. */
  padding: 0 16px;
  background-color: var(--md-sys-color-surface-container-highest);
  border: none;
  border-bottom: 1px solid var(--md-sys-color-on-surface-variant);
  border-radius: var(--md-sys-shape-corner-extra-small) var(--md-sys-shape-corner-extra-small) 0 0;
  box-shadow: none;
  font-size: 16px;                 /* body-large; also stops iOS zoom-on-focus */
  line-height: 24px;
  letter-spacing: .5px;
  color: var(--md-sys-color-on-surface);
  transition: border-color var(--md-sys-motion-short),
              background-color var(--md-sys-motion-short);
}
.form-control:focus {
  border-bottom: 2px solid var(--md-sys-color-primary);
  background-color: var(--md-sys-color-surface-container-high);
  box-shadow: none;
  outline: none;
}
.form-control[disabled],
.form-control[readonly],
fieldset[disabled] .form-control {
  background-color: rgba(27,27,33,.04);
  border-bottom-style: dotted;
  color: rgba(27,27,33,.38);
}
.form-control::placeholder { color: var(--md-sys-color-on-surface-variant); opacity: 1; }

/* The label above the field. Bootstrap puts it outside, so it is styled as M3's
   supporting label rather than the floating one — the floating variant would
   need markup on 275 fields. */
.form-group > label,
.control-label {
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: .5px;            /* label-medium */
  color: var(--md-sys-color-on-surface-variant);
  margin-bottom: 6px;
}

/* ── A file input, wearing the same clothes as every field beside it ────────
 
   A file input cannot take `.form-control`: Bootstrap 3's own docs say so, and
   its fixed 56px height clips the native control. The app's answer is
   `.file-control`, which four screens had not been given — they carried
   `.form-control` with `height: auto` instead, and rendered 24px tall against
   a 56px text field, with the browser's grey "Choose File" button poking out
   of the squashed strip. That is the mismatch that gets noticed: not a wrong
   colour, a control that plainly belongs to a different program.
 
   `.file-control` lived in bridge.css wearing the LEGACY theme — #f8f8f8 with
   a 3px #ddd border — which was right when the app looked like that and is now
   its own mismatch. It is redefined here, beside the field styles it has to sit
   next to, from the same tokens: the same fill, the same underline, the same
   radius, the same 16px type.
 
   `::file-selector-button` is the part that was never styled at all. It is the
   button the operating system draws, and it is the thing somebody means when
   they say a file picker "has the default style". */
#root .file-control,
.file-control {
  display: block;
  width: 100%;
  height: auto;
  min-height: var(--nl-field-h);
  padding: 12px 16px;
  background-color: var(--md-sys-color-surface-container-highest);
  border: none;
  border-bottom: 1px solid var(--md-sys-color-on-surface-variant);
  border-radius: var(--md-sys-shape-corner-extra-small) var(--md-sys-shape-corner-extra-small) 0 0;
  box-shadow: none;
  font-size: 16px;
  line-height: 24px;
  letter-spacing: .5px;
  color: var(--md-sys-color-on-surface);
  cursor: pointer;
  transition: border-color var(--md-sys-motion-short),
              background-color var(--md-sys-motion-short);
}

#root .file-control:hover:not(:disabled) {
  background-color: var(--md-sys-color-surface-container-high);
}

#root .file-control:focus,
#root .file-control:focus-visible {
  border-bottom: 2px solid var(--md-sys-color-primary);
  background-color: var(--md-sys-color-surface-container-high);
  outline: none;
  box-shadow: none;
}

#root .file-control:disabled {
  background-color: var(--md-sys-color-surface-container);
  color: rgba(0, 0, 0, .38);
  cursor: not-allowed;
}

/* The button the OS draws. Made to match the app's outlined button, so the
   picker reads as part of the form rather than as a piece of the browser. */
#root .file-control::file-selector-button {
  margin-right: 16px;
  padding: 6px 16px;
  background: transparent;
  border: 1px solid var(--md-sys-color-outline, var(--md-sys-color-on-surface-variant));
  border-radius: 20px;
  color: var(--md-sys-color-primary);
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: .1px;
  cursor: pointer;
  transition: background-color var(--md-sys-motion-short);
}

#root .file-control::file-selector-button:hover {
  background-color: rgba(0, 0, 102, .08);
}

#root .file-control:disabled::file-selector-button {
  color: rgba(0, 0, 0, .38);
  cursor: not-allowed;
}

/* A textarea grows; a fixed 56px would crop it.
 
   The floor is FOUR lines, not two.
 
   96px minus 32px of padding is 64px, which at a 24px line-height is two and a
   half lines — so a field declared `rows={2}` showed two and a half lines of a
   paragraph however much page there was around it. Graham asked three times for
   description boxes big enough "so one can see what they are typing"; this is
   the half of that answer which applies everywhere at once, and the individual
   screens he named by URL were widened as well.
 
   `resize: vertical` is explicit rather than left to the browser default: a
   floor is not a ceiling, and somebody writing a long brief should be able to
   drag it taller. Horizontal resizing is off because it breaks the grid the
   field sits in. */
textarea.form-control {
  height: auto;
  min-height: 128px;
  padding: 16px;
  line-height: 24px;
  resize: vertical;
}

/* Native select: strip the OS control and draw M3's trailing chevron. */
select.form-control {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  padding-right: 44px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%2346464f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 20px 20px;
  cursor: pointer;
}
/* The DataTables chrome hard-codes `.dataTables_filter input{height:26px
   !important; border-radius:3px}`, so this is the one control that cannot be
   reached by specificity. Named explicitly rather than escalating .input-sm
   globally, and it is the only !important outside the block further down. */
.dataTables_filter input.form-control,
.dataTables_length select.form-control {
  height: var(--nl-field-h-sm) !important;
  line-height: 20px;
  border-radius: var(--md-sys-shape-corner-extra-small) !important;
}

/* An auto-width select — the "Show N entries" control is the one that bites —
   has no 44px to spare for the chevron, and the value gets crushed under it. */
.dataTables_length select.form-control,
.form-inline select.form-control,
.input-sm[size="1"],
select.input-sm {
  padding-right: 28px;
  background-position: right 6px center;
  background-size: 16px 16px;
}

/* A list-box select (size/multiple) is not a 56px control. */
select.form-control[multiple],
select.form-control[size]:not([size="1"]) {
  height: auto;
  padding: 8px;
  background-image: none;
}

/* Date and time inputs are native here too — give the indicator room and tint
   it to the brand rather than leaving it browser-grey. */
input[type="date"].form-control,
input[type="datetime-local"].form-control,
input[type="time"].form-control,
input[type="month"].form-control { padding-right: 12px; }
input[type="date"].form-control::-webkit-calendar-picker-indicator,
input[type="datetime-local"].form-control::-webkit-calendar-picker-indicator,
input[type="time"].form-control::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: .65;
  filter: invert(9%) sepia(79%) saturate(3000%) hue-rotate(238deg) brightness(60%) contrast(115%);
}
input[type="date"].form-control::-webkit-calendar-picker-indicator:hover { opacity: 1; }

/* Density -2 for fields that sit inside dense furniture. A 56px control in a
   table cell or a filter bar is what would make this restyle unliveable. */
.input-sm,
.table .form-control,
.form-inline .form-control,
.input-group-sm > .form-control {
  height: var(--nl-field-h-sm);
  padding: 0 12px;
  font-size: 14px;
  line-height: 20px;
}
.table .form-control { border-radius: var(--md-sys-shape-corner-extra-small); }

/* The navigation bar is 50px tall. A field sized for a page body is clipped by
   it, which is what made the campus switcher render as an empty box. */
.main-header .form-control,
.navbar .form-control {
  height: 34px;
  padding: 0 12px;
  font-size: 13px;
  line-height: 34px;
  border-radius: var(--md-sys-shape-corner-extra-small);
  border-bottom-width: 1px;
}
.main-header select.form-control,
.navbar select.form-control {
  padding-right: 34px;
  background-position: right 10px center;
  background-size: 18px 18px;
}

/* Input groups: Bootstrap aligns addons to a 34px control; at 56px they detach.
 
   LAID OUT AS FLEX, not as Bootstrap 3's `display: table`. The addon used to be
   given `display: inline-flex` to centre its contents at 56px, which takes it
   out of the table row — so a group with two addons before the field (the
   answer-option editor: a radio, then the letter, then the answer) wrapped the
   field onto a second line and left the remove button spanning both. Bootstrap
   4 and 5 both moved input groups to flex for the same reason; doing it here
   keeps the vertical centring the addon needs without fighting a table.
 
   The field takes `width: 1%` with `flex: 1 1 auto`, which is the standard way
   to let a flex item shrink below its content: without `min-width: 0` a long
   placeholder would push the addons off the end. */
.input-group { display: flex; align-items: stretch; }
.input-group > .form-control,
.input-group > .m3-select,
.input-group > .m3-date {
  flex: 1 1 auto;
  width: 1%;
  min-width: 0;
  float: none;
}
.input-group > .input-group-addon,
.input-group > .input-group-btn {
  flex: 0 0 auto;
  width: auto;
  display: flex;
  align-items: center;
}
.input-group .form-control { border-radius: var(--md-sys-shape-corner-extra-small) 0 0 0; }
.input-group-addon {
  height: var(--nl-field-h);
  padding: 0 16px;
  background-color: var(--md-sys-color-surface-container-high);
  border: none;
  border-bottom: 1px solid var(--md-sys-color-on-surface-variant);
  border-radius: var(--md-sys-shape-corner-extra-small) 0 0 0;
  color: var(--md-sys-color-on-surface-variant);
  font-size: 14px;
  display: inline-flex;
  align-items: center;
}
/* A radio or checkbox in an addon is the control, not a bullet beside text —
   it gets the whole addon and sits on its centre line. */
.input-group-addon > input[type="radio"],
.input-group-addon > input[type="checkbox"] { margin: 0; }
.input-group-btn .btn { height: var(--nl-field-h); border-radius: 0 var(--md-sys-shape-corner-extra-small) 0 0; }

/* Validation. */
.has-error .form-control { border-bottom-color: var(--md-sys-color-error); }
.has-error .form-control:focus { border-bottom-color: var(--md-sys-color-error); }
.has-error .control-label,
.help-block.text-danger,
.text-danger { color: var(--md-sys-color-error-text); }

/* ------------------------------------------- checkbox, radio and switches -- */
/* accent-color paints native controls without replacing them — no markup and
   no plugin, and the keyboard and screen-reader behaviour stay native. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--md-sys-color-primary);
  width: 18px;
  height: 18px;
  cursor: pointer;
}
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
  outline: 3px solid var(--md-sys-color-primary);
  outline-offset: 2px;
}

/* --------------------------------------------------------------- surfaces -- */
/* M3 cards: corner-medium, elevation 1, no hard border. */
.box,
.panel,
.small-box,
.info-box {
  border-radius: var(--md-sys-shape-corner-medium);
  border: none;
  border-top: none;
  box-shadow: var(--md-sys-elevation-1);
  background-color: var(--md-sys-color-surface-container-lowest);
}
.box-header,
.panel-heading {
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium) var(--md-sys-shape-corner-medium) 0 0;
  background: transparent;
  padding: 16px 20px;
}
.box-header .box-title,
.panel-title {
  font-size: 16px;
  line-height: 24px;
  font-weight: 500;
  letter-spacing: .15px;           /* title-medium */
  color: var(--md-sys-color-on-surface);
}
.box-body, .panel-body { padding: 20px; }

/* ----------------------------------------------------------------- tables -- */
/* M3 has no data-table component; these follow its list and density guidance. */
.table { color: var(--md-sys-color-on-surface); }
.table > thead > tr > th {
  height: var(--nl-row-h);
  padding: 0 16px;
  vertical-align: middle;
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
  background: var(--md-sys-color-surface-container-low);
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: .5px;            /* label-medium */
  text-transform: uppercase;
  color: var(--md-sys-color-on-surface-variant);
}
.table > tbody > tr > td {
  height: var(--nl-row-h);
  padding: 0 16px;
  vertical-align: middle;
  border-top: 1px solid var(--md-sys-color-outline-variant);
  font-size: 14px;
  letter-spacing: .25px;
}
.table-hover > tbody > tr:hover { background-color: rgba(0,0,102,.05); }
.table-striped > tbody > tr:nth-of-type(odd) { background-color: var(--md-sys-color-surface-container-low); }

/* -------------------------------------------- beating the legacy !important --
   The mirrored AdminLTE sheet sets colour AND background with !important on one
   grouped selector covering .callout.callout-*, .alert-*, .label-* and the
   modal variants — for example:

     .callout.callout-info,.alert-info,.label-info,… { background:#16b1c5!important }
     .callout.callout-danger,…,.label-success,…      { color:#fff!important }

   There is no specificity that beats !important, so these declarations carry it
   too. It is confined to this block deliberately: everything above wins on
   source order alone, and adding !important where it is not needed is how a
   stylesheet becomes impossible to override later.
   ------------------------------------------------------------------------- */

/* ------------------------------------------------------- chips and badges -- */
/* M3 chips: 32dp, corner-small. */
.label {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 10px;
  border-radius: var(--md-sys-shape-corner-small);
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: .5px;
  text-shadow: none;
}
.label-primary { background-color: var(--md-sys-color-primary-container) !important; color: var(--md-sys-color-on-primary-container) !important; }
.label-success { background-color: var(--nl-success-container) !important; color: var(--nl-on-success-container) !important; }
.label-warning { background-color: var(--nl-warning-container) !important; color: var(--nl-on-warning-container) !important; }
.label-danger  { background-color: var(--md-sys-color-error-container) !important; color: var(--md-sys-color-on-error-container) !important; }
.label-info    { background-color: var(--md-sys-color-secondary-container) !important; color: var(--md-sys-color-on-secondary-container) !important; }
.label-default { background-color: var(--md-sys-color-surface-container-highest) !important; color: var(--md-sys-color-on-surface-variant) !important; }
.badge {
  background-color: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
  border-radius: var(--md-sys-shape-corner-full);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .5px;
  padding: 3px 8px;
}

/* ------------------------------------------------------- menus and modals -- */
.dropdown-menu {
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-small);
  box-shadow: var(--md-sys-elevation-3);
  background-color: var(--md-sys-color-surface-container-lowest);
  padding: 8px 0;
  transform-origin: top center;
  animation: m3-menu-in 150ms cubic-bezier(.2, 0, 0, 1);
}
.dropdown-menu > li > a {
  height: 48px;
  display: flex;
  align-items: center;
  padding: 0 12px;
  font-size: 14px;
  letter-spacing: .25px;
  color: var(--md-sys-color-on-surface);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus { background-color: rgba(0,0,102,.08); color: var(--md-sys-color-on-surface); }
.dropdown-menu > .active > a { background-color: var(--md-sys-color-secondary-container); color: var(--md-sys-color-on-secondary-container); }

/* M3 dialogs are corner-extra-large — the one place 28px is correct. */
.modal-content {
  border: none;
  border-radius: var(--md-sys-shape-corner-extra-large);
  box-shadow: var(--md-sys-elevation-3);
  background-color: var(--md-sys-color-surface-container-high);
}
.modal-header { border-bottom: none; padding: 24px 24px 16px; }
.modal-title { font-size: 24px; line-height: 32px; font-weight: 400; color: var(--md-sys-color-on-surface); }
.modal-body { padding: 0 24px 16px; font-size: 14px; line-height: 20px; letter-spacing: .25px; }
.modal-footer { border-top: none; padding: 16px 24px 24px; display: flex; justify-content: flex-end; gap: 8px; }
.modal-footer .btn + .btn { margin-left: 0; }

/* A WIDE dialog is a working surface, not an alert.
 
   M3 puts a dialog on `surface-container-high` and a filled text field on
   `surface-container-highest` — two steps apart on a small confirm box, and
   almost the same colour when the dialog is full of fields. The question
   builder was the proof: a lavender ground carrying lavender fields, with
   nothing but a 5% difference to say where one ended and the other began.
 
   Wide dialogs here are closer to M3's FULL-SCREEN dialog, which sits on plain
   surface, so they take the white and the fields get their contrast back. The
   header and footer rules do the separating that the tint used to attempt. */
.modal-lg .modal-content { background-color: var(--md-sys-color-surface-container-lowest); }
.modal-lg .modal-header {
  padding: 20px 24px;
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
}
.modal-lg .modal-title { font-size: 22px; line-height: 28px; font-weight: 500; }
.modal-lg .modal-body { padding: 20px 24px; }
.modal-lg .modal-footer {
  padding: 16px 24px;
  border-top: 1px solid var(--md-sys-color-outline-variant);
}

/* ------------------------------------------------------------- alert/note -- */
.alert {
  border: none;
  border-radius: var(--md-sys-shape-corner-medium);
  padding: 16px;
  font-size: 14px;
  letter-spacing: .25px;
}
.alert-success { background-color: var(--nl-success-container) !important; color: var(--nl-on-success-container) !important; }
.alert-info    { background-color: var(--md-sys-color-secondary-container) !important; color: var(--md-sys-color-on-secondary-container) !important; }
.alert-warning { background-color: var(--nl-warning-container) !important; color: var(--nl-on-warning-container) !important; }
.alert-danger  { background-color: var(--md-sys-color-error-container) !important; color: var(--md-sys-color-on-error-container) !important; }

/* AdminLTE callouts ship aqua (#00c0ef), which Colleen's guideline names as
   off-brand. M3 has no left-accent callout, so these become tonal containers —
   which also drops the coloured-left-border cliche. */
.callout {
  border: none;
  border-left: none;
  border-radius: var(--md-sys-shape-corner-medium);
  padding: 16px;
  box-shadow: none;
  font-size: 14px;
  line-height: 20px;
  letter-spacing: .25px;
}
.callout p,
.callout h4,
.callout a { color: inherit !important; }
.callout-info,
.callout.callout-info    { background-color: var(--md-sys-color-secondary-container) !important; color: var(--md-sys-color-on-secondary-container) !important; }
.callout-success,
.callout.callout-success { background-color: var(--nl-success-container) !important; color: var(--nl-on-success-container) !important; }
.callout-warning,
.callout.callout-warning { background-color: var(--nl-warning-container) !important; color: var(--nl-on-warning-container) !important; }
.callout-danger,
.callout.callout-danger  { background-color: var(--md-sys-color-error-container) !important; color: var(--md-sys-color-on-error-container) !important; }

/* ------------------------------------------------------------ pagination -- */
.pagination > li > a,
.pagination > li > span {
  border: none;
  margin: 0 2px;
  min-width: 40px;
  height: 40px;
  line-height: 40px;
  text-align: center;
  border-radius: var(--md-sys-shape-corner-full);
  color: var(--md-sys-color-on-surface-variant);
  background: transparent;
}
.pagination > .active > a,
.pagination > .active > span,
.pagination > .active > a:hover {
  background-color: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
  border: none;
}

/* ---------------------------------------------------------------- motion -- */
@media (prefers-reduced-motion: reduce) {
  .btn, .form-control, .table-hover > tbody > tr { transition: none; }
}

/* ------------------------------------------------------- M3 date picker --- */
/* The docked date picker rendered by components/DateField.tsx. The native
   calendar button is hidden rather than the whole native input replaced — see
   that file for why the input underneath stays type="date". */
.m3-date { position: relative; display: block; width: 100%; }

/* BOTH types. `datetime-local` was missed when DateTimeField was added, so
   Chrome's own indicator went on rendering UNDER our button — two calendars,
   the native one offset up-and-right and tinted navy by the
   `input[type="datetime-local"].form-control::-webkit-calendar-picker-indicator`
   rule further up this file, which gives it a brand colour and made it look
   deliberate. */
.m3-date input[type="date"]::-webkit-calendar-picker-indicator,
.m3-date input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  display: none;
}
.m3-date input[type="date"]::-webkit-inner-spin-button,
.m3-date input[type="datetime-local"]::-webkit-inner-spin-button {
  display: none;
}
.m3-date input[type="date"],
.m3-date input[type="datetime-local"] { padding-right: 48px; }
.m3-date .input-sm,
.m3-date .form-inline input[type="date"] { padding-right: 40px; }

.m3-date__trigger {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--md-sys-shape-corner-full);
  background: transparent;
  color: var(--md-sys-color-on-surface-variant);
  cursor: pointer;
  transition: background-color var(--md-sys-motion-short), color var(--md-sys-motion-short);
}
.m3-date__trigger:hover  { background-color: rgba(0,0,102,.08); color: var(--md-sys-color-primary); }
.m3-date__trigger:active { background-color: rgba(0,0,102,.12); }
/* Offset 0, not 2. The button is 40px inside a 48px field, so 3px of ring at
   2px of offset draws a 50px circle in a 48px box and the top and bottom get
   clipped by the field — which is most of why it read as a separate object
   rather than as this control's focus. 40 + 3 + 3 = 46 fits. The 3px thickness
   is Material's and stays. */
.m3-date__trigger:focus-visible { outline: 3px solid var(--md-sys-color-primary); outline-offset: 0; }
.m3-date__trigger[disabled] { color: rgba(27,27,33,.38); cursor: not-allowed; background: transparent; }

/* Docked picker: corner-large, elevation 2, surface-container-high. */
.m3-date__panel {
  position: absolute;
  z-index: 1060;               /* above .box and a Bootstrap modal body */
  transform-origin: top center;
  animation: m3-menu-in 150ms cubic-bezier(.2, 0, 0, 1);
  top: calc(100% + 6px);
  right: 0;
  width: 328px;
  padding: 12px;
  box-sizing: border-box;
  border-radius: var(--md-sys-shape-corner-large);
  background-color: var(--md-sys-color-surface-container-lowest);
  border: 1px solid var(--md-sys-color-outline-variant);
  box-shadow: var(--md-sys-elevation-3);
  color: var(--md-sys-color-on-surface);
}
.m3-date__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 4px 10px;
}
.m3-date__month {
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: .1px;        /* title-small */
}
.m3-date__nav {
  width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border: none; border-radius: var(--md-sys-shape-corner-full);
  background: transparent; color: var(--md-sys-color-on-surface-variant); cursor: pointer;
}
.m3-date__nav:hover { background-color: rgba(0,0,102,.08); }
.m3-date__nav:focus-visible { outline: 3px solid var(--md-sys-color-primary); outline-offset: 2px; }

.m3-date__weekdays,
.m3-date__grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 2px;
}
.m3-date__weekdays span {
  height: 32px;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; line-height: 16px; font-weight: 500; letter-spacing: .5px;
  color: var(--md-sys-color-on-surface-variant);
}
.m3-date__grid:focus-visible { outline: 3px solid var(--md-sys-color-primary); outline-offset: 3px; border-radius: 8px; }
.m3-date__pad { height: 40px; }

.m3-date__day {
  height: 40px;
  border: none;
  border-radius: var(--md-sys-shape-corner-full);
  background: transparent;
  color: var(--md-sys-color-on-surface);
  font-family: inherit;
  font-size: 14px;
  letter-spacing: .25px;
  cursor: pointer;
  transition: background-color var(--md-sys-motion-short), color var(--md-sys-motion-short);
}
.m3-date__day:hover:not([disabled]) { background-color: rgba(0,0,102,.08); }
.m3-date__day.is-today  { box-shadow: inset 0 0 0 1px var(--md-sys-color-primary); color: var(--md-sys-color-primary); }
.m3-date__day.is-cursor { box-shadow: inset 0 0 0 2px var(--md-sys-color-outline); }
.m3-date__day.is-selected,
.m3-date__day.is-selected.is-today,
.m3-date__day.is-selected.is-cursor {
  background-color: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
  box-shadow: none;
}
/* HOVER MUST NOT WIPE OUT THE SELECTED FILL.
   `.m3-date__day:hover:not([disabled])` is (0,3,0) — the class, the pseudo-
   class and the attribute inside :not() all count — and `.is-selected` is only
   (0,2,0). So while the pointer was still resting on the day just clicked, the
   navy went back to an 8% tint while the text stayed `on-primary` white:
   measured `color: rgb(255,255,255)` on `rgba(0,0,102,0.08)`, which is white on
   near-white and reads as an empty circle. That is how the day you just picked
   became the one you could not see.

   Material composites a state layer OVER the container rather than replacing
   it, so hover stays perceptible: the fill is still primary with a white 8%
   film on top. */
.m3-date__day.is-selected:hover:not([disabled]),
.m3-date__day.is-selected.is-today:hover:not([disabled]),
.m3-date__day.is-selected.is-cursor:hover:not([disabled]) {
  background-color: var(--md-sys-color-primary);
  background-image: linear-gradient(rgba(255,255,255,.10), rgba(255,255,255,.10));
  color: var(--md-sys-color-on-primary);
}
.m3-date__day[disabled] { color: rgba(27,27,33,.38); cursor: not-allowed; }

/* ------------------------------ the time half of the datetime picker ------ */
/* Material 3's time picker has a dial variant and an INPUT variant. This is the
   input: two numeric fields and a colon. The dial is a poor fit docked beneath
   a calendar, and these are administrative fields where somebody knows the time
   they want and types it. 24-hour, which is what the value format is and what
   South Africa writes. */
.m3-date__time {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 8px;
  padding: 12px 4px 4px;
  border-top: 1px solid var(--md-sys-color-outline-variant);
}
.m3-date__time-label {
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: .5px;              /* label-medium */
  color: var(--md-sys-color-on-surface-variant);
}
.m3-date__time-fields { display: inline-flex; align-items: center; gap: 4px; }
.m3-date__time-unit { position: relative; display: inline-flex; align-items: center; gap: 4px; }

/* The list each box opens on focus. Scrolls rather than growing: 24 hours at
   40px would be taller than the calendar above it. */
.m3-date__time-list {
  position: absolute;
  z-index: 1;
  /* UPWARD. The time row is the last thing in the panel before Clear/Now/Done,
     so a list opening downward always covers those three and then spills past
     the panel's own edge onto the page. Over the calendar is the only direction
     with room. */
  bottom: calc(100% + 4px);
  right: 0;
  width: 72px;
  max-height: 180px;
  overflow-y: auto;
  margin: 0;
  padding: 4px;
  list-style: none;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-small);
  background: var(--md-sys-color-surface-container-lowest);
  box-shadow: var(--md-sys-elevation-2);
  scrollbar-width: thin;
}
.m3-date__time-option {
  display: block;
  width: 100%;
  height: 36px;
  padding: 0;
  border: none;
  border-radius: var(--md-sys-shape-corner-small);
  background: transparent;
  color: var(--md-sys-color-on-surface);
  font-family: inherit;
  font-size: 15px;
  line-height: 36px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.m3-date__time-option:hover { background-color: rgba(0,0,102,.08); }
.m3-date__time-option.is-selected {
  background-color: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
  font-weight: 500;
}
/* Same trap as the calendar's selected day: hover is (0,2,0) with the
   pseudo-class and would otherwise wipe the fill out from under white text. */
.m3-date__time-option.is-selected:hover {
  background-color: var(--md-sys-color-primary);
  background-image: linear-gradient(rgba(255,255,255,.10), rgba(255,255,255,.10));
  color: var(--md-sys-color-on-primary);
}
.m3-date__time-input {
  width: 56px;
  height: 48px;
  padding: 0;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-small);
  background: var(--md-sys-color-surface-container-highest);
  color: var(--md-sys-color-on-surface);
  font-family: inherit;
  font-size: 22px;
  line-height: 1;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.m3-date__time-input:focus {
  outline: none;
  border-color: var(--md-sys-color-primary);
  box-shadow: inset 0 0 0 1px var(--md-sys-color-primary);
}
.m3-date__time-colon { font-size: 20px; color: var(--md-sys-color-on-surface-variant); }

.m3-date__actions { display: flex; justify-content: flex-end; gap: 4px; padding-top: 8px; }
/* `Done` is the only affirmative action in the panel — the others clear or
   jump — so it carries the weight. */
.m3-date__text--primary { font-weight: 600; }
.m3-date__text {
  height: 40px; padding: 0 12px;
  border: none; border-radius: var(--md-sys-shape-corner-full);
  background: transparent; color: var(--md-sys-color-primary);
  font-family: inherit; font-size: 14px; font-weight: 500; letter-spacing: .1px;
  cursor: pointer;
}
.m3-date__text:hover:not([disabled]) { background-color: rgba(0,0,102,.08); }
.m3-date__text[disabled] { color: rgba(27,27,33,.38); cursor: not-allowed; }

/* Near the right-hand edge the panel would overflow; flip it to the left. */
@media (max-width: 480px) {
  .m3-date__panel { right: auto; left: 0; width: min(328px, calc(100vw - 32px)); }
}

@media (prefers-reduced-motion: reduce) {
  .m3-date__trigger, .m3-date__day, .m3-date__text { transition: none; }
}

/* ----------------------------------------------------------- M3 select ---- */
/* The menu rendered by components/SelectField.tsx over a native <select>.
   The select itself keeps the .form-control styling above — this is only the
   popup that replaces the browser's, and only for pointer users. */
.m3-select { position: relative; display: block; width: 100%; }

/* Bootstrap gives a .form-control in an inline context display:inline-block and
   width:auto. The wrapper has to inherit that or the control stacks — which is
   what turned "Show 25 entries" into three lines. */
.form-inline .m3-select,
.dataTables_length .m3-select,
.dataTables_filter .m3-select,
.input-group .m3-select {
  display: inline-block;
  width: auto;
  vertical-align: middle;
}

/* A box header is the same inline context under another name. Without this a
   SelectField dropped beside the Cards/Table/Add buttons lands on its own line
   ABOVE them, because the wrapper is display:block and the inline style a
   bare <select> used to carry now reaches only the inner select. */
.box-tools .m3-select {
  display: inline-block;
  width: auto;
  vertical-align: middle;
  margin-right: 6px;
}

/* M3 menu: corner-extra-small, elevation 2, surface-container. */
.m3-select__menu {
  position: absolute;
  z-index: 1060;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  max-height: 320px;
  overflow-y: auto;
  padding: 8px 0;
  box-sizing: border-box;
  border-radius: var(--md-sys-shape-corner-small);
  /* SOLID, not a tint. surface-container is a 5% navy wash, which over the
     navy panel read as a translucent sheet rather than a menu sitting above
     the page. A menu is an opaque surface with real elevation. */
  background-color: var(--md-sys-color-surface-container-lowest);
  border: 1px solid var(--md-sys-color-outline-variant);
  box-shadow: var(--md-sys-elevation-3);
  transform-origin: top center;
  animation: m3-menu-in 150ms cubic-bezier(.2, 0, 0, 1);
}

/* M3 menus grow from their anchor rather than appearing. 150ms is the spec's
   short duration — long enough to read as motion, short enough that nobody
   waits for it. */
@keyframes m3-menu-in {
  from { opacity: 0; transform: translateY(-4px) scaleY(.96); }
  to   { opacity: 1; transform: none; }
}

.m3-select__item {
  display: flex;
  align-items: center;
  width: 100%;
  height: 48px;                    /* M3 menu item */
  padding: 0 12px;
  border: none;
  background: transparent;
  color: var(--md-sys-color-on-surface);
  font-family: inherit;
  font-size: 14px;
  line-height: 20px;
  letter-spacing: .25px;
  text-align: left;
  cursor: pointer;
  transition: background-color var(--md-sys-motion-short);
}
.m3-select__item:hover:not([disabled]) { background-color: rgba(0,0,102,.08); }
/* The keyboard highlight. Focus really is on this option while the menu is
   open, so this is the browser's own focus — it only needs to be VISIBLE, and
   an M3 menu marks the focused item with a state layer rather than a ring that
   would sit half under the item above. The outline is kept as well, because a
   state layer alone is a colour difference and colour alone is not a focus
   indicator. */
.m3-select__item:focus { outline: none; background-color: rgba(0,0,102,.12); }
.m3-select__item:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: -2px;
}
.m3-select__item.is-selected {
  background-color: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
  font-weight: 500;
}
.m3-select__item[disabled] { color: rgba(27,27,33,.38); cursor: not-allowed; }

.m3-select__group {
  padding: 12px 12px 6px;
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: .5px;            /* label-medium */
  color: var(--md-sys-color-on-surface-variant);
}

/* In the dark navigation bar the menu still belongs to the page, not the bar. */
.main-header .m3-select__menu { color: var(--md-sys-color-on-surface); }

@media (prefers-reduced-motion: reduce) {
  .m3-select__item { transition: none; }
}

/* ------------------------------------------------ M3 navigation drawer ---- */
/* The left panel as a Material 3 standard navigation drawer.
   https://m3.material.io/components/navigation-drawer
 
   Restyled in place: the markup stays AdminLTE's treeview, so no menu logic
   moves and the collapse behaviour, active-group tracking and permission
   dimming all keep working.
 
   WIDTH IS UNCHANGED on purpose. M3 specifies 360dp, but the width is wired
   into .content-wrapper, .main-header and .main-footer margins and the
   sidebar-collapse state; moving it is a layout change rather than a restyle,
   and worth doing separately if it is wanted.
 
   The drawer is LIGHT because that is what the spec says. The masthead above
   it keeps the brand navy, which is the pairing M3 calls a top app bar over a
   surface drawer. */
/* .skin-blue qualifies the drawer too:
 
     .skin-blue .wrapper, .skin-blue .main-sidebar, .skin-blue .left-side
       { background-color:#222533; box-shadow: 0 0 3px 6px … }        (0,2,0)
 
   A bare `.main-sidebar` is (0,2,0)... no: it is (0,1,0), and loses. This was
   measured, not assumed — the drawer stayed #222533 while the width and the
   label colours took, which left grey labels on a dark ground at 2.4:1. The
   wrapper is in the same rule and is the page's own ground, so it moves too;
   that is what removes the dark strip below the footer.
 
   The heavy shadow goes: an M3 standard drawer sits at elevation 0. */
.skin-blue .wrapper,
.skin-blue .main-sidebar,
.skin-blue .left-side {
  background-color: var(--md-sys-color-surface-container-low);
  box-shadow: none;
}
.skin-blue .wrapper { background-color: var(--md-sys-color-surface-container-low); }
.main-sidebar .sidebar {
  padding: 8px 0 24px;
  /* Full height of the panel, not of the viewport — bridge.css sizes this for
     a drawer that ran edge to edge. */
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
}

/* A default scrollbar on a navy panel is a bright stripe. */
.main-sidebar .sidebar::-webkit-scrollbar { width: 6px; }
.main-sidebar .sidebar::-webkit-scrollbar-track { background: transparent; }
.main-sidebar .sidebar::-webkit-scrollbar-thumb {
  background-color: rgba(255,255,255,.22);
  border-radius: 3px;
}
.main-sidebar .sidebar::-webkit-scrollbar-thumb:hover { background-color: rgba(255,255,255,.34); }
.main-sidebar .sidebar { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.22) transparent; }

/* The nested list has its own dark ground in the skin. */
.skin-blue .sidebar-menu > li > .treeview-menu { background: transparent; margin: 0; }

/* The session strip, and the box that was visible at the top of the drawer.
 
   Two separate causes, both confirmed by dumping the rules that match the
   element rather than by guessing:
 
     .fixedmenu { background: #222533 !important }   ss-main-darkblue.css
     .fixedmenu { width: 200px }                     style-main.css
 
   The first is why it stayed dark on a light drawer; the second is why it
   stopped short of the edge after the drawer went to 260px. It is also
   `ul.sessionul`, OUTSIDE .sidebar-menu, which is why the earlier
   `.sidebar-menu > li.removehover` rule never reached it at all. */
.main-sidebar .sessionul,
.main-sidebar .sessionul.fixedmenu {
  list-style: none;
  margin: 0;
  /* 8px at the top so the first row clears the panel's 12px corner radius.
     Where there is no campus picker above it — the student and parent shells —
     this row IS the top of the panel. */
  padding: 8px 0;
  width: auto;
  background: transparent !important;
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
}
.main-sidebar .sessionul > li,
.main-sidebar .sessionul > li.removehover,
.main-sidebar .sessionul > li.accurrent {
  background: transparent;
  border: none;
  /* `display: flex` so the li is exactly as tall as its child, and `padding: 0`
     because the legacy sheet puts `padding-bottom: 8px` on it. That 8px was the
     whole fault: the strip measured 65px instead of 56 with 8px above the row
     and 17px below, which pushed the divider away from the text. */
  display: flex;
  padding: 0;
  margin: 0;
}
/* `.accurrent { padding: 0 0 8px !important }` in style-main.css is the whole
   fault, and !important is the only thing that reaches it. That 8px made the
   strip 65px instead of 56 with 8px above the row and 17px below, pushing the
   divider away from the text. */
.main-sidebar .sessionul > li.accurrent { padding: 0 !important; }
/* `a` AND `span`. The admin shell writes this row as `<li><a role="button">`
   and the student and parent shells write it as `<li><span>` — a statement of
   fact rather than a control, which is correct. Styling only the anchor left
   the student's session line with no box at all: flush against the panel edge
   and clipped by its rounded corner. */
.main-sidebar .sessionul > li > a,
.main-sidebar .sessionul > li > span {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 40px;
  margin: 0 8px;
  padding: 0 14px;
  border-radius: var(--md-sys-shape-corner-full);
  background: transparent;
  color: var(--md-sys-color-on-surface-variant);
  font-size: 13px;
  letter-spacing: .25px;
}
.main-sidebar .sessionul > li > a > .pull-right { margin-left: auto; float: none; }
.main-sidebar .sessionul > li > a[href]:hover {
  background-color: rgba(0,0,102,.08);
  color: var(--md-sys-color-on-surface);
}
.main-sidebar .sessionul > li.accurrent > a { cursor: default; }

/* The destinations clear the session strip's divider.
   `.sessionul` ends in a 1px border and `.sidebar-menu` began immediately
   after it, so with a destination ACTIVE its 56dp pill — full bleed,
   corner-full, white — started 2px under that line and the two read as one
   collided object. It only showed in the student and parent shells: the admin
   drawer has the Quick Links row between the divider and the first
   destination.

   The space goes on the STRIP as a margin, not on the list as padding.
   `.sidebar-menu` also carries `.verttop`, and style-main.css says
   `.verttop { padding-top: 0 !important }` — which no selector can outweigh,
   because !important is not reachable by specificity. Margin is untouched by
   that rule, and "space after the section break" is where it belongs anyway.
   8px here plus the item's own 2px margin is Material's 8dp list padding. */
.main-sidebar .sessionul,
.main-sidebar .sessionul.fixedmenu { margin-bottom: 8px; }

/* Section headers — label-medium, not a coloured band. */
.sidebar-menu > li.header {
  height: 48px;
  display: flex;
  align-items: center;
  padding: 0 28px;
  background: transparent;
  border: none;
  color: var(--md-sys-color-on-surface-variant);
  font-size: 12px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: .5px;
  text-transform: none;
}

/* Every destination is a 56dp pill inset from the drawer edge. */
.sidebar-menu > li > a {
  position: relative;
  height: 56px;
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 2px 8px;
  padding: 0 14px;
  border-radius: var(--md-sys-shape-corner-full);
  border-left: none;                /* AdminLTE marks the active item with a bar */
  white-space: nowrap;
  overflow: hidden;
  background: transparent;
  color: var(--md-sys-color-on-surface-variant);
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: .1px;             /* label-large */
  transition: background-color var(--md-sys-motion-short), color var(--md-sys-motion-short);
}
/* A destination is one line. At this drawer width "Student Information" wraps
   at M3's 16px padding, and a two-line nav item reads as a mistake. */
.sidebar-menu > li > a > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidebar-menu > li > a > i,
.sidebar-menu > li > a > .fa {
  width: 22px;
  flex: 0 0 22px;
  font-size: 17px;
  text-align: center;
  color: inherit;
}
.main-sidebar .sidebar-menu > li > a,
.main-sidebar .sidebar-menu > li > a > span,
.main-sidebar .sidebar-menu > li > a > i { color: var(--md-sys-color-on-surface-variant); }
.main-sidebar .sidebar-menu > li > a:hover,
.main-sidebar .sidebar-menu > li > a:hover > span,
.main-sidebar .sidebar-menu > li > a:hover > i {
  background-color: transparent;
  color: var(--md-sys-color-on-surface);
}
.main-sidebar .sidebar-menu > li > a:hover { background-color: rgba(0,0,102,.08); }
.main-sidebar .sidebar-menu > li.active > a,
.main-sidebar .sidebar-menu > li.menu-open > a {
  background-color: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
  border-left: none;
  font-weight: 500;
}
.main-sidebar .sidebar-menu > li.active > a > i,
.main-sidebar .sidebar-menu > li.active > a > span,
.main-sidebar .sidebar-menu > li.menu-open > a > i,
.main-sidebar .sidebar-menu > li.menu-open > a > span {
  color: var(--md-sys-color-on-secondary-container);
}
.sidebar-menu > li > a:focus-visible {
  outline: 3px solid var(--md-sys-color-primary);
  outline-offset: -3px;
}

/* The expand chevron sits at the trailing edge, not floated. */
.sidebar-menu > li > a > .pull-right-container {
  position: static;
  margin-left: auto;
  display: flex;
  align-items: center;
}
.sidebar-menu > li > a > .pull-right-container > .fa { font-size: 14px; }

/* Nested destinations: shorter, indented under their group, same pill. */
.sidebar-menu .treeview-menu {
  padding: 2px 0 6px;
  background: transparent;
}
.sidebar-menu .treeview-menu > li > a {
  height: 44px;
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 1px 12px 1px 28px;
  padding: 0 16px;
  border-radius: var(--md-sys-shape-corner-full);
  color: var(--md-sys-color-on-surface-variant);
  font-size: 14px;
  letter-spacing: .25px;
  font-weight: 400;
}
.sidebar-menu .treeview-menu > li > a > i,
.sidebar-menu .treeview-menu > li > a > .fa { width: 18px; font-size: 13px; color: inherit; }
.sidebar-menu .treeview-menu > li > a:hover {
  background-color: rgba(0,0,102,.08);
  color: var(--md-sys-color-on-surface);
}
.main-sidebar .sidebar-menu .treeview-menu > li.active > a,
.main-sidebar .sidebar-menu .treeview-menu > li.active > a > span {
  background-color: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
  font-weight: 500;
}
.main-sidebar .sidebar-menu .treeview-menu > li.active > a > span { background: transparent; }

/* The session strip above the menu is information, not a destination. Its
   own background was sized to the old drawer width and left a white block
   stopping short of the edge. */
.sidebar-menu > li.removehover,
.sidebar-menu > li.accurrent {
  background: transparent;
  border: none;
}
.sidebar-menu > li.removehover > a,
.sidebar-menu > li.removehover > a:hover {
  background: transparent;
  color: var(--md-sys-color-on-surface-variant);
  cursor: default;
  height: 40px;
  font-weight: 400;
  font-size: 13px;
}

/* A destination the person's permissions do not reach stays visible and dimmed
   — M3's disabled treatment — rather than vanishing, which is what the
   sidebar's existing lock affordance depends on. */
.sidebar-menu li.is-locked > a,
.sidebar-menu .treeview-menu > li.is-locked > a {
  color: rgba(0,0,0,.38);
  cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
  .sidebar-menu > li > a { transition: none; }
}

/* The drawer is 200px in the legacy sheet, which cannot hold "Student
   Information" at M3's label-large — the labels truncated. M3 specifies 360dp
   for a standard drawer; that is too much to take from a screen full of wide
   tables, so this goes to 260px, the width at which the longest destination
   fits on one line.
 
   Three values move together. The content, footer and right side are positioned
   by margin against this width, and the collapsed state slides the drawer out
   by exactly its own width — miss either and the layout tears. The 50px mini
   state is left alone; it carries !important and is a different design. */
.main-sidebar,
.left-side { width: 260px; }
.content-wrapper,
.right-side,
.main-footer { margin-left: 260px; }
.sidebar-collapse .main-sidebar,
.sidebar-collapse .left-side { transform: translate(-260px, 0); }
@media (max-width: 767px) {
  .main-sidebar,
  .left-side { transform: translate(-260px, 0); }
  .content-wrapper,
  .right-side,
  .main-footer { margin-left: 0; }
}

/* --------------------------------------------------- M3 top app bar ------- */
/* https://m3.material.io/components/top-app-bar

   EVERY SELECTOR HERE CARRIES .skin-blue, to match the skin's own weight:

     .skin-blue .main-header .navbar { background-color: #222533 }   (0,3,0)

   A bare `.main-header .navbar` is (0,2,0) and loses no matter where it sits
   in the file. Matching the weight and coming later is what decides it — the
   same trap the navigation drawer's active pill fell into.

   HEIGHT IS UNCHANGED at 50px. M3 specifies 64dp, but the height is wired into
   the sidebar's padding-top, the logo slot and .content-wrapper's min-height
   calc, and 14px of chrome on every screen of a dense admin is a poor trade
   for a number — M3's density scale exists for exactly this. What makes this a
   Material app bar is the surface, the type and the state layers.

   The masthead lockup changed with it: this ground is light now, so
   PortalHeader takes the navy lockup rather than the reverse one. */
/* The rule under the bar lives on .main-header, NOT on its two halves.
   The logo slot and the navbar are sized independently — the navbar's height
   comes from its content — so a border on each drew two lines a pixel apart.
   One element, one line, whatever the halves do. */
.main-header { border-bottom: 1px solid var(--md-sys-color-outline-variant); }
.skin-blue .main-header .navbar {
  background-color: var(--md-sys-color-surface);
  border-bottom: none;
  box-shadow: none;
}
.skin-blue .main-header .logo,
.skin-blue .main-header .logo:hover {
  background-color: var(--md-sys-color-surface);
  border-bottom: none;
  color: var(--md-sys-color-on-surface);
}

/* Actions: on-surface-variant at rest, an M3 state layer on hover. */
.skin-blue .main-header .navbar .nav > li > a,
.skin-blue .main-header .navbar .sidebar-toggle {
  color: var(--md-sys-color-on-surface-variant);
  text-shadow: none;
}
.skin-blue .main-header .navbar .nav > li > a:hover,
.skin-blue .main-header .navbar .nav > li > a:active,
.skin-blue .main-header .navbar .nav > li > a:focus,
.skin-blue .main-header .navbar .nav .open > a,
.skin-blue .main-header .navbar .nav .open > a:hover,
.skin-blue .main-header .navbar .nav .open > a:focus,
.skin-blue .main-header .navbar .nav > .active > a,
.skin-blue .main-header .navbar .sidebar-toggle:hover {
  background: rgba(0,0,102,.08);
  color: var(--md-sys-color-on-surface);
}
.skin-blue .main-header .navbar .nav > li > a:focus-visible,
.skin-blue .main-header .navbar .sidebar-toggle:focus-visible {
  outline: 3px solid var(--md-sys-color-primary);
  outline-offset: -3px;
}

/* The campus switcher reads as an outlined control on a light bar; the filled
   treatment it inherits from .form-control would look like a hole. */
.main-header .form-control {
  background-color: transparent;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-small);
  color: var(--md-sys-color-on-surface);
}
.main-header .form-control:focus {
  border: 1px solid var(--md-sys-color-primary);
  background-color: transparent;
}
.main-header select.form-control {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23666666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
}

/* Text that used to sit on the dark bar. */
.skin-blue .main-header .sidebar-session,
.skin-blue .main-header .navbar .navbar-text,
.skin-blue .main-header .navbar .hidden-xs { color: var(--md-sys-color-on-surface); }

/* The notification badge keeps brand red, now on a light ground. */
.main-header .navbar .label-danger,
.main-header .navbar .badge {
  background-color: var(--md-sys-color-error) !important;
  color: var(--md-sys-color-on-error) !important;
}

/* The currency picker carries `.currency-icon-list .btn-default{color:#fff
   !important}` from the legacy sheet — chosen for the dark bar, and invisible
   on a surface. !important is the only thing that reaches it. */
.main-header .currency-icon-list .btn-default,
.main-header .currency-icon-list button {
  color: var(--md-sys-color-on-surface-variant) !important;
}

/* The avatar link inherits the legacy link teal (#0084b4). It wraps an image so
   nothing paints it today, but a focus ring or any text added later would. */
.main-header .ssuserleft a { color: var(--md-sys-color-on-surface-variant); }

/* ------------------------------------------------- M3 account menu -------- */
/* The avatar menu behind the bar's last action, as a Material 3 menu: the same
   surface, elevation, corner and 48dp items as `.m3-select__menu`, because a
   product with two kinds of menu has neither.

   What it replaced was AdminLTE's user panel — a 90px grey clip-art bust that
   is nobody's photograph, a name set as an <h4> so it was the largest type on
   screen, and two actions floated to opposite ends of one line where neither
   was a real target. The legacy sheet fights every one of those with
   `!important` and fixed pixel sizes, so several rules below carry it back;
   each one says which declaration it is reaching. */

/* The initials avatar. A circle of brand tint with two letters in it — no
   request, nobody's face, and no assumption about anybody's gender, which the
   hardcoded `default_male.jpg` made about every administrator and guardian. */
.m3-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: var(--md-sys-shape-corner-full);
  background: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .5px;
  line-height: 1;
  text-transform: uppercase;
  user-select: none;
}
.m3-avatar--sm { width: 32px; height: 32px; font-size: 12px; }
/* On the bar the avatar IS the button's visible body, so it carries the ring. */
.main-header .user-menu > .dropdown-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
  cursor: pointer;
}
.main-header .user-menu > .dropdown-toggle:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: 2px;
  border-radius: var(--md-sys-shape-corner-full);
}

.main-header .user-menu .dropdown-menu.m3-account-menu {
  /* 8px, not 0. `.headertopmenu { margin-right: -5px }` in the legacy sheet
     pulls the last action past its own container, so `right: 0` put the menu's
     edge exactly on the window's — measured 1440 of 1440, with no gutter at
     all. This lands it level with the other bar actions. */
  right: 8px;
  left: auto;
  min-width: 264px;
  padding: 8px;
  margin-top: 4px;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  background-color: var(--md-sys-color-surface-container-lowest);
  /* `.menuboxshadow` sets a 26%-black drop shadow chosen for a dark bar. */
  box-shadow: var(--md-sys-elevation-3) !important;
  animation: m3-menu-in 150ms cubic-bezier(.2, 0, 0, 1);
  transform-origin: top right;
}
/* `.menuboxshadow:before` draws a caret out of borders, pinned `right: 10px`.
   It only ever lined up with one trigger width, and a Material menu does not
   have one — it grows from its anchor instead. */
.main-header .user-menu .m3-account-menu::before,
.main-header .user-menu .m3-account-menu::after { content: none !important; }

/* The identity row. A menu header, not a hero: 40dp avatar, body-large name,
   body-small role. It is also a link to the dashboard, which is why it takes a
   state layer like an item. */
/* EVERY RULE BELOW CARRIES `.skin-blue`, and it is not decoration.
   skin-darkblue.css has, inside `@media (max-width: 767px)`:

     .skin-blue .main-header .navbar .dropdown-menu li a
       { display: block; padding: 3px 20px }

   which is (0,4,2). On a phone it turned this row back into a block — avatar
   on one line, name on the next — and the row's own `overflow: hidden` then
   clipped the name away entirely, so the menu showed an avatar and nothing
   else. A four-class selector does not reach it; `.skin-blue` makes these
   (0,5,1), which does. */
.skin-blue .main-header .user-menu .m3-account-menu > li > .m3-account {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px;
  border-radius: var(--md-sys-shape-corner-small);
  color: var(--md-sys-color-on-surface) !important;   /* .sspass a wins otherwise */
  text-decoration: none;
  transition: background-color var(--md-sys-motion-short);
}
.skin-blue .main-header .user-menu .m3-account-menu > li > .m3-account:hover,
.skin-blue .main-header .user-menu .m3-account-menu > li > .m3-account:focus-visible { background-color: rgba(0,0,102,.08); }
.m3-account__text { display: flex; flex-direction: column; min-width: 0; }
.m3-account__name {
  font-size: 16px;
  line-height: 24px;
  font-weight: 500;                 /* body-large, medium */
  letter-spacing: .15px;
  text-transform: capitalize;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.m3-account__role {
  font-size: 12px;
  line-height: 16px;
  letter-spacing: .4px;             /* body-small */
  color: var(--md-sys-color-on-surface-variant);
  text-transform: capitalize;
}

.m3-account-menu__divider {
  height: 1px;
  margin: 8px 0;
  background: var(--md-sys-color-outline-variant);
  list-style: none;
}

/* 48dp items, stacked. They were side by side on one line — "Change Password"
   left and a `.pull-right` "Logout" — so the pair measured 20px tall between
   them and read as a footer rather than as two choices. */
.skin-blue .main-header .user-menu .m3-account-menu > li > .m3-account-menu__item {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 48px;
  padding: 0 12px;
  border-radius: var(--md-sys-shape-corner-small);
  color: var(--md-sys-color-on-surface) !important;   /* .sspass a { color: #333 !important } */
  font-size: 14px;
  line-height: 20px;
  letter-spacing: .1px;             /* label-large */
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--md-sys-motion-short);
}
.skin-blue .main-header .user-menu .m3-account-menu > li > .m3-account-menu__item:hover,
.skin-blue .main-header .user-menu .m3-account-menu > li > .m3-account-menu__item:focus-visible {
  /* `.sspass a:hover` repaints to #0084B4 with !important — the legacy teal
     the brand guideline rules out. */
  background-color: rgba(0,0,102,.08) !important;
  color: var(--md-sys-color-on-surface) !important;
  text-decoration: none;
}
.skin-blue .main-header .user-menu .m3-account-menu > li > .m3-account-menu__item > .fa {
  width: 20px;
  font-size: 16px;
  text-align: center;
  color: var(--md-sys-color-on-surface-variant);
}

@media (prefers-reduced-motion: reduce) {
  .main-header .user-menu .dropdown-menu.m3-account-menu { animation: none; }
}


/* Links in the content area carry the legacy teal (#0084b4), which the brand
   guideline rules out. Scoped to the content area and excluding anything that
   is really a control, so it cannot repaint buttons or chips. */
.content-wrapper a:not(.btn) { color: var(--md-sys-color-primary); }
.content-wrapper a:not(.btn):hover { color: #00004d; }

/* =============================================================================
   BRAND-FORWARD SHELL
   =============================================================================
   The spec-correct M3 drawer was near-white with a 3% navy tint, so the brand
   showed up only on an active pill and a couple of buttons — accurate to
   Material and unrecognisable as Northlink.

   This takes the floating-panel pattern used by Material dashboard layouts
   (a panel inset from the viewport with a large radius and a soft shadow, the
   active destination as a filled button) and fills it with the college's own
   colours: the panel is brand navy, so the brand is the first thing on screen
   rather than a detail. The pattern is adapted, not copied — no other
   product's palette, type or assets are used.
   ========================================================================== */

/* The panel floats: inset from the edges, large radius, its own shadow. */
.skin-blue .main-sidebar,
.skin-blue .left-side {
  top: 66px;                        /* 50px bar + 16px inset */
  left: 16px;
  bottom: 16px;
  height: auto;
  min-height: 0;
  padding-top: 0;
  border-radius: var(--md-sys-shape-corner-medium);
  /* The panel must NOT scroll. bridge.css already scrolls the inner .sidebar,
     so giving the panel its own overflow produced two stacked scrollbars —
     and the inner one is sized `calc(100vh - 50px)`, which is taller than the
     floating panel, so both of them actually overflowed. `hidden` here also
     clips the content to the rounded corners. */
  overflow: hidden;
  background-image: linear-gradient(to bottom right, #000066 0%, #00003d 100%);
  background-color: #000066;
  box-shadow: var(--md-sys-elevation-2);
}
.content-wrapper,
.right-side,
.main-footer { margin-left: 292px; }   /* 16 inset + 260 panel + 16 gap */
.sidebar-collapse .main-sidebar,
.sidebar-collapse .left-side { transform: translate(-292px, 0); }

/* The page behind it. */
.skin-blue .wrapper { background-color: var(--md-sys-color-surface-container-low); }
.content-wrapper { background-color: var(--md-sys-color-surface-container-low); }

/* Destinations on navy. */
.main-sidebar .sidebar-menu > li > a,
.main-sidebar .sidebar-menu > li > a > span,
.main-sidebar .sidebar-menu > li > a > i { color: rgba(255,255,255,.78); }
.main-sidebar .sidebar-menu > li > a:hover,
.main-sidebar .sidebar-menu > li > a:hover > span,
.main-sidebar .sidebar-menu > li > a:hover > i { color: #ffffff; }
.main-sidebar .sidebar-menu > li > a:hover { background-color: rgba(255,255,255,.12); }

/* The active destination is a filled button, as in the reference — white here,
   because a navy fill on a navy panel is a button nobody can see. */
.main-sidebar .sidebar-menu > li.active > a,
.main-sidebar .sidebar-menu > li.menu-open > a {
  background-color: #ffffff;
  box-shadow: 0 2px 6px rgba(0,0,0,.28);
}
.main-sidebar .sidebar-menu > li.active > a,
.main-sidebar .sidebar-menu > li.active > a > span,
.main-sidebar .sidebar-menu > li.active > a > i,
.main-sidebar .sidebar-menu > li.menu-open > a,
.main-sidebar .sidebar-menu > li.menu-open > a > span,
.main-sidebar .sidebar-menu > li.menu-open > a > i { color: var(--md-sys-color-primary); }

/* Nested destinations. */
.main-sidebar .sidebar-menu .treeview-menu > li > a,
.main-sidebar .sidebar-menu .treeview-menu > li > a > i { color: rgba(255,255,255,.66); }
.main-sidebar .sidebar-menu .treeview-menu > li > a:hover {
  background-color: rgba(255,255,255,.12);
  color: #ffffff;
}
.main-sidebar .sidebar-menu .treeview-menu > li.active > a,
.main-sidebar .sidebar-menu .treeview-menu > li.active > a > span {
  background-color: rgba(255,255,255,.18);
  color: #ffffff;
}
.main-sidebar .sidebar-menu > li.header { color: rgba(255,255,255,.55); }

/* A destination the person cannot reach: dimmed against navy, not against white. */
.main-sidebar .sidebar-menu li.is-locked > a,
.main-sidebar .sidebar-menu .treeview-menu > li.is-locked > a { color: rgba(255,255,255,.34); }

/* The session strip, on navy. */
.main-sidebar .sessionul,
.main-sidebar .sessionul.fixedmenu { border-bottom: 1px solid rgba(255,255,255,.16); }
.main-sidebar .sessionul > li > a,
.main-sidebar .sessionul > li > span { color: rgba(255,255,255,.72); }
.main-sidebar .sessionul > li > a[href]:hover {
  background-color: rgba(255,255,255,.12);
  color: #ffffff;
}

/* Cards pick up the reference's softer, larger-radius treatment. */
.box, .panel, .small-box, .info-box {
  border-radius: var(--md-sys-shape-corner-medium);
  box-shadow: 0 2px 8px rgba(0,0,102,.08), 0 1px 3px rgba(0,0,102,.06);
}

/* The content-link rule started as `a:not(.btn):not(.label):not(.badge):not(
   .m3-select__item)`. EACH :not() ARGUMENT ADDS SPECIFICITY, so that was
   (0,5,1) and outranked the component rules it had no business touching — the
   active page number came out navy on a navy circle at 1.00:1.
 
   Three of those exclusions were never needed: .m3-select__item and
   .m3-date__day are <button>, and .label/.badge are not anchors here. So the
   rule is just `a:not(.btn)` (0,2,1) now, and the components below win on
   their own weight rather than needing !important. */
.content-wrapper .pagination > .active > a,
.content-wrapper .pagination > .active > span,
.content-wrapper .pagination > .active > a:hover {
  color: var(--md-sys-color-on-primary);
}
.content-wrapper .pagination > li > a,
.content-wrapper .pagination > li > span { color: var(--md-sys-color-on-surface-variant); }
.content-wrapper .dropdown-menu > li > a { color: var(--md-sys-color-on-surface); }
.content-wrapper .dropdown-menu > .active > a { color: var(--md-sys-color-on-secondary-container); }

/* ------------------------------------------- ONE BASELINE IN THE BAR ------ */
/* The bar had THREE vertical baselines at once. Measured on the student shell
   at 1440, in a 57px bar whose centre is y=29:

     logo                     centre y=25
     campus name              centre y=20
     language + currency      centre y=20
     name, bell, avatar       centre y=28

   Because it is a float layout: three floated regions — the menu button and
   two Bootstrap columns at percentage widths — each sitting at the top of the
   line with padding standing in for centring. Floats cannot be made to agree
   on a baseline, so the row becomes one flex line at EVERY width and in every
   shell. It was already doing this below 768px, where the button and the
   campus label were otherwise drawn on top of each other; the same reasoning
   applies at every other width and the split was arbitrary.

   `position: relative` on the button is load-bearing: style-main.css says
   `.sidebar-toggle { position: absolute }`, which takes it out of the flex
   line entirely — with the flex change alone the overlap measured 40px,
   exactly as before. `.pull-right` inside the second column still floats as
   it always did. */
.main-header .navbar {
  display: flex;
  align-items: center;
  gap: 4px;
}
.main-header .navbar > .sidebar-toggle {
  float: none;
  position: relative;
  flex: 0 0 auto;
}
.main-header .navbar > div[class*="col-"] { float: none; width: auto; padding: 0; }
.main-header .navbar > div[class*="col-"]:nth-of-type(1) { flex: 1 1 auto; min-width: 0; }
.main-header .navbar > div[class*="col-"]:nth-of-type(2) { flex: 0 0 auto; }

/* THE CAMPUS NAME. ss-main-darkblue.css has

     .main-header .sidebar-session {
       color: rgb(176,221,56); font-size: 20px; font-weight: bold;
       float: left; padding: 10px 0 10px 40px }

   — lime green, 20px, bold, and padded rather than centred, which is the 9px
   it sat above everything else. It is CONTEXT, not a page title: the name of
   the campus whose data is on screen, said in the bar's own voice.

   `.skin-blue` on the selector because an earlier rule of this file's own,
   `.skin-blue .main-header .sidebar-session`, sets `color: on-surface` at
   (0,3,0) and was quietly beating the (0,2,0) rule that set the muted colour.
   Two rules of mine disagreeing is worse than the skin disagreeing with me. */
.skin-blue .main-header .sidebar-session,
.main-header .sidebar-session {
  /* A BLOCK with an absolutely-placed glyph, not a flex row.
     `text-overflow: ellipsis` applies to a block container's inline content; a
     bare text node inside a flex container is an anonymous flex item and is
     not inline content, so as a flex row this clipped mid-word — "Goodw" with
     no ellipsis — at 390px. */
  position: relative;
  display: block;
  float: none;
  padding: 0 0 0 27px;
  margin: 0;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  color: var(--md-sys-color-on-surface);
  /* title-large: 22/28, weight 400, tracking 0 — Material 3's headline for a
     small top app bar. 14px read as a caption someone forgot to finish; this
     is the size the slot is specified at. */
  font-size: 22px;
  line-height: 28px;
  font-weight: 400;
  letter-spacing: 0;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The glyph is what makes it read as SCOPE rather than as a page title — the
   same move a workspace or project picker makes. on-surface-variant so the
   name stays the thing you read first. */
.skin-blue .main-header .sidebar-session::before,
.main-header .sidebar-session::before {
  content: "\f19c";                 /* fa-university */
  font-family: FontAwesome;
  font-size: 17px;
  line-height: 1;
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  color: var(--md-sys-color-on-surface-variant);
}

/* The right-hand actions are three separate blocks — the language picker, the
   currency picker and the `ul.nav` of icons — and the first two FLOAT inside
   `.navbar-custom-menu`, so they sat at the top of it however they were
   centred internally. The container is the thing that has to be a flex row;
   centring inside `.langdiv` moved nothing because `.langdiv` itself was the
   element at y=0. */
.main-header .navbar-custom-menu {
  display: flex;
  align-items: center;
}
.main-header .navbar-custom-menu > * { float: none; }

.main-header .langdiv,
.main-header .currency-icon-list {
  display: flex;
  align-items: center;
}
/* The .btn-group wrapper sits between the flex container and the button, so
   centring the container alone moved nothing — the group was centred and the
   button still sat at the top of it. Measured 8px high either way. */
.main-header .langdiv .btn-group,
.main-header .currency-icon-list .btn-group {
  display: flex;
  align-items: center;
  float: none;
}
.main-header .langdiv .btn,
.main-header .currency-icon-list .btn {
  display: inline-flex;
  align-items: center;
  height: 40px;
  line-height: 1;
}
/* The flag. `.flag-icon` is a background-image span the vendored sheet sets to
   `display: block`, so inside the 27px `.filter-option` box it sat on the
   bottom edge — measured 6px below the bar's centre while its own parent was
   dead centre. The label is a flex row now and the flag an item in it. */
.main-header .filter-option {
  display: inline-flex !important;   /* .pull-left floats it otherwise */
  align-items: center;
  gap: 6px;
  float: none !important;
}
/* `.main-header .filter-option .flag-icon`, three classes, because style-main
   has `.filter-option span.flag-icon { margin-top: 13px }` at (0,2,1) — a
   hand-tuned top margin that fake-centred the flag in the old 50px bar and is
   the whole 6px. A two-class selector loses to it. The gap on .filter-option
   replaces the 3px right margin from the rule above it. */
.main-header .filter-option .flag-icon,
.main-header .flag-icon {
  display: inline-block;
  flex: 0 0 auto;
  line-height: 1;
  margin: 0;
  float: none;
}

/* The lockup is an <img> on a text baseline inside its slot, so it sat 3px
   high — the descender space under it is not part of the image. Centred in
   the slot instead of aligned to a line box that has no text in it. */
.main-header .logo {
  display: flex;
  align-items: center;
}
/* NOT `display: flex` on the two slots. AdminLTE shows one and hides the other
   with `display`, so forcing it on both put the collapsed emblem on screen
   NEXT TO the full lockup — two marks in the bar. The descender gap is the
   actual cause of the 3px, and `display: block` on the image removes it
   without touching which slot is visible. */
.main-header .logo img { display: block; }

/* The student's name beside the actions. It carried an inline
   `line-height: 50px` — a 50px line in a 56px bar, centring by luck and by
   four pixels — which no stylesheet could reach. The inline style is gone from
   PortalHeader; this replaces it, where it can be seen next to the rest of the
   bar. */
.main-header .nav > li.cal15 > .topbar-name {
  display: inline-flex;
  align-items: center;
  height: 40px;
  font-size: 12px;
  line-height: 1;
  letter-spacing: .4px;
  color: var(--md-sys-color-on-surface-variant);
}

/* ------------------------------------------------ masthead alignment ------ */
/* Three things were out of step once the drawer began to float, all measured
   rather than guessed:
 
     logo slot   x=0   w=200  height=50      <- 200px was the OLD drawer width
     navbar      x=200 w=1080 height=55      <- 5px taller, so the rule under
                                                the logo sat 5px high
     panel       x=16  w=260  top=66         <- 66 assumed a 50px bar, leaving
                                                an 11px gap instead of 16px
 
   So: the two halves of the bar are the same height and their bottom rules
   join; the brand block is the same width as the panel and starts at the same
   x, so the lockup sits directly above it; and the panel clears the bar's real
   height.
 
   Scoped to the expanded state. The collapsed 50px rail is a different design
   with its own widths, and these would fight it. */
/* DESKTOP ONLY. These were scoped to `body:not(.sidebar-collapse)` but not to
   a width, so a 390px screen still got a 260px logo slot and a navbar pushed
   276px to the right — 666px of header inside a 390px viewport. The narrow
   layout is a different shape and is set out in its own block below. */
@media (min-width: 768px) {
  .main-header .logo { height: 56px; }   /* the navbar's own content makes it 56 */
  body:not(.sidebar-collapse) .main-header .logo {
    margin-left: 16px;
    width: 260px;
  }
  body:not(.sidebar-collapse) .main-header .navbar { margin-left: 276px; }
  /* The bar is fixed and 57px with its rule; AdminLTE's `fixed` layout clears
     only 50px, so the content sat 7px under it. */
  .fixed .content-wrapper { padding-top: 57px; }
}

.skin-blue .main-sidebar,
.skin-blue .left-side { top: 73px; }   /* 57px bar (56 + its 1px rule) + 16px inset */

/* --------------------------------------------------------------- footer --- */
/* A one-line copyright does not need a band of its own. AdminLTE gives it a
   panel with a hard top border, which read as an orphaned grey strip next to
   the floating drawer. It sits on the page surface instead, as body-small in
   the same muted colour the drawer's labels use, aligned to the content's
   right edge so it lines up with the cards above it. */
.main-footer {
  background: transparent;
  border-top: none;
  box-shadow: none;
  /* 10px right, because that is the content area's own inset — measured from
     a card's right edge, not picked. At 24px the copyright sat 14px inside
     the cards above it, which is the sort of near-miss that looks like a bug. */
  padding: 12px 10px 24px;
  color: var(--md-sys-color-on-surface-variant);
  font-size: 12px;
  line-height: 16px;
  letter-spacing: .4px;
  text-align: right;
}
.main-footer a { color: var(--md-sys-color-primary); }

/* ----------------------------------------------------- the menu button ---- */
/* AdminLTE's toggle is a floated anchor with `padding: 17px 15px 13px` and a
   FontAwesome glyph — asymmetric padding standing in for centring, which is
   why it never sat straight in the bar. It is a Material icon button now: a
   40dp target with a circular state layer, vertically centred by its own
   margins (8 + 40 + 8 = 56, the bar's height).
 
   The float stays. The navbar is a float layout and the actions on the right
   are `pull-right`; taking it out of the flow moves things that have nothing
   to do with this button. */
.main-header .navbar .sidebar-toggle {
  width: 40px;
  height: 40px;
  margin: 8px 4px 8px 8px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--md-sys-shape-corner-full);
  font-size: 18px;
  line-height: 1;
  transition: background-color var(--md-sys-motion-short), color var(--md-sys-motion-short);
}
.main-header .navbar .sidebar-toggle::before { line-height: 1; display: block; }
/* Bootstrap's three bars are in the markup but the glyph is what renders; keep
   them from ever showing up beside it. */
.main-header .navbar .sidebar-toggle > .icon-bar { display: none; }

/* --------------------------------------------- campus picker in the panel -- */
/* The choice sits beside what it scopes, rather than in the bar. It is a
   tonal control on the navy panel: the filled .form-control treatment would
   read as a hole punched in the gradient. */
.main-sidebar .m3-campus {
  position: relative;
  /* Above the session strip, which is `position: sticky; z-index: 2` from the
     legacy sheet. At the same z-index the strip wins on document order and
     paints straight through the open menu — which looks exactly like a
     translucent dropdown and is not: the menu is opaque white, it was simply
     being painted over. */
  z-index: 30;
  padding: 14px 12px 12px;
  border-bottom: 1px solid rgba(255,255,255,.16);
}
.main-sidebar .m3-campus__label {
  display: block;
  margin: 0 0 6px 4px;
  font-size: 11px;
  line-height: 16px;
  font-weight: 500;
  letter-spacing: .5px;
  text-transform: uppercase;
  color: rgba(255,255,255,.62);
}
.main-sidebar .m3-campus .m3-select { width: 100%; }
.main-sidebar .m3-campus__select {
  height: 44px;
  width: 100%;
  padding: 0 14px;
  padding-right: 40px;
  background-color: rgba(255,255,255,.12);
  border: none;
  border-radius: var(--md-sys-shape-corner-small);
  color: #ffffff;
  font-size: 14px;
  letter-spacing: .25px;
  /* White chevron: the shared one is drawn in on-surface-variant for a light
     field and disappears here. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 20px 20px;
}
.main-sidebar .m3-campus__select:hover { background-color: rgba(255,255,255,.18); }
.main-sidebar .m3-campus__select:focus {
  background-color: rgba(255,255,255,.18);
  outline: 2px solid #ffffff;
  outline-offset: -2px;
}

/* The panel clipped its own corners with overflow:hidden, which would also
   clip this menu. The gradient is clipped by border-radius on its own, so the
   scroll container keeps the overflow and the panel does not. */
.skin-blue .main-sidebar,
.skin-blue .left-side { overflow: visible; }
.main-sidebar .sidebar { border-radius: inherit; }

/* ============================================================================
   DRAWER BEHAVIOUR
   ============================================================================
   The 72px icon rail is gone. This navigation is built from EXPANDABLE GROUPS —
   "Academics" holds nine destinations — and a rail hides exactly the sub-items
   people navigate by, so collapsing turned a usable menu into sixteen
   unlabelled glyphs. The campus control had nothing to show at that width
   either.

   Material's own answer is two drawers, not one that shrinks:

     desktop      a STANDARD drawer, permanently open, no toggle at all
     small screen a MODAL drawer over the content, with a scrim, opened by the
                  menu button — which is the only place that button earns its
                  keep

   https://m3.material.io/components/navigation-drawer
   ========================================================================== */

@media (min-width: 768px) {
  /* No toggle: display:none takes it out of the accessibility tree too, so it
     is not a button hidden from sight but still reachable by keyboard. */
  .main-header .navbar .sidebar-toggle { display: none; }

  /* Permanently open, whatever the collapse class says. React can still set
     `sidebar-collapse` (from a narrow window, or a stale preference) and the
     desktop layout must not care. */
  .sidebar-mini.sidebar-collapse .main-sidebar,
  .sidebar-mini.sidebar-collapse .left-side,
  .skin-blue.sidebar-collapse .main-sidebar {
    width: 260px !important;
    transform: none !important;
  }
  .sidebar-mini.sidebar-collapse .content-wrapper,
  .sidebar-mini.sidebar-collapse .right-side,
  .sidebar-mini.sidebar-collapse .main-footer { margin-left: 292px !important; }
  .sidebar-mini.sidebar-collapse .main-header .logo { width: 260px; margin-left: 16px; }
  .sidebar-mini.sidebar-collapse .main-header .navbar { margin-left: 276px; }
  /* Labels and groups stay, because there is no rail to hide them for. */
  .sidebar-mini.sidebar-collapse .sidebar-menu > li > a > span,
  .sidebar-mini.sidebar-collapse .sidebar-menu > li > .treeview-menu,
  .sidebar-mini.sidebar-collapse .sidebar-menu > li > a > .pull-right,
  .sidebar-mini.sidebar-collapse .main-sidebar .sessionul,
  .sidebar-mini.sidebar-collapse .main-sidebar .m3-campus { display: block !important; }
  .sidebar-mini.sidebar-collapse .sidebar-menu > li > a > span { display: inline !important; }
}

/* Below 768px the drawer is modal: it sits over the page rather than pushing
   it, and the menu button is how it is opened and closed. */
@media (max-width: 767px) {
  .main-sidebar,
  .left-side {
    z-index: 1200;
    transform: translate(-292px, 0);
    transition: transform var(--md-sys-motion-medium);
  }
  .sidebar-collapse .main-sidebar,
  .sidebar-collapse .left-side { transform: translate(0, 0) !important; }
  .content-wrapper,
  .right-side,
  .main-footer { margin-left: 0 !important; }

}

@media (prefers-reduced-motion: reduce) {
  .m3-select__menu,
  .m3-date__panel,
  .dropdown-menu,
  .main-sidebar { animation: none; transition: none; }
}

/* On a narrow screen the 260px lockup cannot share the bar with the menu
   button and the actions, so the masthead takes the emblem alone — which is
   what the collapsed slot used to do, moved to the width that actually needs
   it now that the desktop drawer never collapses. */
@media (max-width: 767px) {
  /* One row: emblem, menu button, actions. AdminLTE stacks the logo above the
     navbar here, which with a fixed bar produced a 100px header the content
     never cleared.

     THE HEADER ITSELF IS THE FLEX ROW at this width, not the navbar alone.
     The logo slot was a fixed 72px holding a 34px emblem left-aligned at x=4,
     so 38px of the slot was empty and the menu button began at 88 — a 50px
     dead zone between the mark and everything else, which is the gap that
     reads as unfinished. A slot sized to its content and a navbar that simply
     follows it removes the measurement entirely rather than re-tuning it.

     The desktop rule is deliberately NOT changed: there the 260px slot is the
     same width as the navigation panel and starts at the same x, so the lockup
     sits directly above it. That alignment is the point of the number. */
  .main-header {
    height: 56px;
    display: flex;
    align-items: center;
  }
  .main-header .logo {
    width: auto;
    flex: 0 0 auto;
    margin-left: 0;
    padding: 0 6px 0 12px;
    height: 56px;
    float: none;
    position: relative;
  }
  .main-header .logo > .logo-lg { display: none; }
  .main-header .logo > .logo-mini { display: block; }
  .main-header .navbar {
    flex: 1 1 auto;
    margin-left: 0;
    width: auto;
    min-width: 0;
    min-height: 56px;
    float: none;
  }
  .fixed .content-wrapper { padding-top: 56px; }

  /* THE BAR CARRIES ONLY WHAT A PHONE NEEDS.
     Seven actions plus a language picker plus a currency picker do not fit in
     390px, so they wrapped onto a second row and covered the page beneath.
     Language and currency are ~150px of chrome nobody changes on a phone. */
  .main-header .langdiv,
  .main-header .currency-icon-list { display: none; }
  .main-header .navbar-custom-menu { float: right; }
  /* title-large is 22px and the phone bar has ~150px for it after the button
     and the actions, so it steps down to title-medium rather than truncating
     a campus name to three letters. */
  /* `.skin-blue` again: the base rule above carries it, so a two-class
     selector here loses and the phone kept the 22px headline. */
  .skin-blue .main-header .sidebar-session,
  .main-header .sidebar-session {
    font-size: 16px;
    line-height: 24px;
    font-weight: 500;
    letter-spacing: .15px;
    padding-left: 23px;
  }
  .skin-blue .main-header .sidebar-session::before,
  .main-header .sidebar-session::before { font-size: 15px; }

  .main-header .navbar-custom-menu > .nav {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
  }
  .main-header .navbar-custom-menu > .nav > li { float: none; }
  .main-header .navbar .nav > li > a { padding-left: 8px; padding-right: 8px; }

  /* A WIDE TABLE SCROLLS RATHER THAN BEING CLIPPED.
     Eight columns cannot fit a phone, and the right-hand ones were simply cut
     off with no affordance. The table keeps its shape and its container
     scrolls — the one place horizontal scrolling is the right answer. */
  .dataTables_wrapper { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .content-wrapper .table { min-width: 560px; }

  /* Cards give back the padding they can spare at this width. */
  .content { padding: 10px; }
  .box-body, .panel-body { padding: 14px; }
  .box-header, .panel-heading { padding: 14px; }

  /* The filter row stacks instead of squeezing. */
  .dataTables_wrapper .row > [class^="col-"] { width: 100%; float: none; }
  .dataTables_filter, .dataTables_length { text-align: left !important; }


  /* TAP TARGETS.
     The legacy sheet's `.btn-xs` is 28px tall and `.btn-sm` 32px; a bare
     checkbox is 18x18. Material's minimum is 48dp and Apple's 44pt, and a
     28px button between two other 28px buttons in a table row is a genuine
     mis-tap every time. 40px is the compromise that keeps a five-row table on
     one screen; the row itself is the wider surface.

     `display: inline-flex` rather than more padding: padding on a .btn-xs
     fights the legacy `padding: 1px 5px`, and centring the label is the point.
     The margin is so they have rhythm once they wrap, which at this width
     they do. */
  .content-wrapper .btn,
  .content-wrapper .btn-xs,
  .content-wrapper .btn-sm {
    min-height: 40px;
    /* Width too: the icon-only buttons in a table's action column came out
       32-35px wide against a 40px height, which is a target you miss from the
       side rather than from above. */
    min-width: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-top: 2px;
    margin-bottom: 2px;
  }
  /* STANDALONE LINKS ARE CONTROLS AND GET A CONTROL'S TARGET.
     A card header's "View all", a card's "View student directory", a table
     cell's record link, a bare chevron in a `.pull-right` — every one measured
     20px tall. They are the only thing in their container, which is what
     separates them from a link inside a sentence: `:only-child` says so
     precisely, and a prose link keeps its line rhythm because this never
     reaches it. */
  .content-wrapper .box-header .box-tools > a,
  .content-wrapper .panel-heading .box-tools > a,
  .content-wrapper td > a:only-child,
  .content-wrapper th > a:only-child,
  .content-wrapper p > a:only-child,
  .content-wrapper .pull-right > a:only-child {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
  }
  .content-wrapper .box-header .box-tools > a,
  .content-wrapper .panel-heading .box-tools > a { padding: 0 8px; }
  /* An icon-only link is 11px of glyph. Height alone left a 11x40 sliver —
     a target you miss from the side rather than from above. */
  .content-wrapper .pull-right > a:only-child {
    min-width: 40px;
    justify-content: center;
  }

  /* The dashboard's notice and assignment lists. Each row is a single
     `.displayinline` link that wraps an icon and a line of text, so it is as
     tall as one line — 24px — and on a phone it is the whole row a thumb aims
     at. It only started failing the sweep when the test bed grew notifications
     addressed to the signed-in student: with an empty list there was nothing
     to be too small. */
  .content-wrapper .media-title > a.displayinline,
  .content-wrapper li.doc-file-type a.displayinline {
    display: flex;
    align-items: center;
    min-height: 40px;
  }

  /* A checkbox cannot be given a 48dp target from CSS alone — `appearance` is
     the browser's and padding does not extend the hit area reliably. 24px is
     what can be done here honestly; the 48dp version needs the input wrapped
     in a label, which is a markup change and not this one. */
  .content-wrapper input[type="checkbox"],
  .content-wrapper input[type="radio"] {
    width: 24px;
    height: 24px;
  }
}

/* The notification bell is its own component and missed the icon-button
   treatment the other bar actions got — it measured 34x50, under the 40px
   minimum target and a different shape from its neighbours. */
.main-header .nbell__trigger {
  width: 40px;
  height: 40px;
  margin: 8px 2px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--md-sys-shape-corner-full);
  line-height: 1;
  vertical-align: middle;
}


/* Every action in the bar is the same icon button. They were a mix of 44x56
   and 34x50 sitting 3px apart vertically, because each came from a different
   part of the legacy header and none of them agreed on a box. */
.main-header .navbar-custom-menu > .nav > li > a,
.main-header .navbar-custom-menu > .nav > li > .nbell__trigger {
  height: 40px;
  min-width: 40px;
  margin: 8px 1px;
  padding: 0 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--md-sys-shape-corner-full);
  line-height: 1;
}
.main-header .navbar-custom-menu > .nav > li { display: flex; align-items: center; }

/* AdminLTE's right-hand control sidebar is not a feature of this product — no
   layout renders anything into it — but all three still render the element
   that paints behind it, parked 230px off the right edge. Nothing draws, and
   it answers every "what is outside the viewport" sweep with a false positive
   for ever. */
.control-sidebar-bg { display: none; }

/* ------------------------------------------------------------- the scrim -- */
/* A real element, rendered by each layout, NOT a pseudo-element: it has to be
   clickable. The open drawer covers the menu button, so tapping outside is the
   only way to dismiss it.
 
   The hidden rule comes FIRST and the media query second. Written the other way
   round — which is how this was first done — the later `display: none` wins at
   equal specificity and the scrim never appears at any width. */
.m3-scrim { display: none; }

@media (max-width: 767px) {
  .m3-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 1150;
    background: rgba(0, 0, 0, .32);
    animation: m3-scrim-in 150ms cubic-bezier(.2, 0, 0, 1);
  }
}
@keyframes m3-scrim-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .m3-scrim { animation: none; } }

/* The campus name in the bar, for the shells that show it as text rather than
   a picker (student, parent). It inherited a heading's weight and size and
   read as the page title; it is context, so it takes the bar's own voice. */
.main-header .sidebar-session {
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  letter-spacing: .1px;
  color: var(--md-sys-color-on-surface-variant);
}

/* ------------------------------------------------- Question builder ------- */
/* The Add Question editor on Online Assessment and the Question Bank.
 
   What it replaced was a Bootstrap striped table with an "ACTION" column, an
   <h4>, and three form groups, all of it painted in the same lavender wash
   because every M3 filled field is `surface-container-highest` and the modal
   body behind them is a container tint too. Grey blocks on a grey ground with
   no card, no rule and no rhythm: the screen carried no hierarchy at all, and
   the one element with a border (the remove button) read as bolted on.
 
   The shape here is what every assessment builder converges on — Canvas,
   Moodle 4, Blackboard Ultra: the paper is a LIST OF CARDS, and composing is a
   distinct surface below it. That distinction is the whole design. The list is
   what exists; the composer is what you are doing. */

/* -- the paper ------------------------------------------------------------- */
.m3-qlist { margin: 0 0 24px; padding: 0; list-style: none; }

.m3-qcard {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 16px;
  margin-bottom: 8px;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  background-color: var(--md-sys-color-surface-container-lowest);
  transition: border-color var(--md-sys-motion-short),
              box-shadow var(--md-sys-motion-short);
}
.m3-qcard:hover {
  border-color: var(--md-sys-color-outline);
  box-shadow: var(--md-sys-elevation-1);
}

/* The number is the question's identity on a printed paper, so it is a real
   badge rather than a table cell that happens to hold a digit. */
.m3-qcard__num {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: var(--md-sys-color-primary-container);
  color: var(--md-sys-color-on-primary-container);
  font-size: 14px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.m3-qcard__body { flex: 1 1 auto; min-width: 0; }

.m3-qcard__prompt {
  margin: 4px 0 8px;
  font-size: 15px;
  line-height: 22px;
  color: var(--md-sys-color-on-surface);
  /* Wraps. A question is prose and truncating it hides the thing being read. */
  overflow-wrap: anywhere;
}

.m3-qcard__meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

.m3-qchip {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 10px;
  border-radius: var(--md-sys-shape-corner-small);
  background-color: var(--md-sys-color-surface-container-high);
  color: var(--md-sys-color-on-surface-variant);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .5px;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Marks are numbers in a column people scan and add up, so tabular figures. */
.m3-qcard__marks {
  font-size: 13px;
  font-weight: 500;
  color: var(--md-sys-color-on-surface-variant);
  font-variant-numeric: tabular-nums;
}

/* Reorder and delete stay out of the way until the card is under the pointer,
   which is what stops three buttons per row competing with the questions. They
   remain visible to the keyboard, because :focus-within keeps them up. */
.m3-qcard__actions {
  flex: 0 0 auto;
  display: flex;
  gap: 4px;
  opacity: .45;
  transition: opacity var(--md-sys-motion-short);
}
.m3-qcard:hover .m3-qcard__actions,
.m3-qcard:focus-within .m3-qcard__actions { opacity: 1; }

.m3-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: none;
  border-radius: var(--md-sys-shape-corner-full);
  background: transparent;
  color: var(--md-sys-color-on-surface-variant);
  font-size: 13px;
  cursor: pointer;
  transition: background-color var(--md-sys-motion-short), color var(--md-sys-motion-short);
}
.m3-icon-btn:hover:not([disabled]) { background-color: rgba(0,0,102,.08); }
.m3-icon-btn:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: -2px;
}
.m3-icon-btn[disabled] { opacity: .38; cursor: not-allowed; }
.m3-icon-btn--danger:hover:not([disabled]) {
  background-color: var(--md-sys-color-error-container);
  color: var(--md-sys-color-error);
}

/* -- the empty paper ------------------------------------------------------- */
.m3-qempty {
  padding: 32px 16px;
  margin-bottom: 24px;
  border: 1px dashed var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  text-align: center;
  color: var(--md-sys-color-on-surface-variant);
}
.m3-qempty i { font-size: 22px; opacity: .5; display: block; margin-bottom: 8px; }
.m3-qempty strong { display: block; font-size: 15px; color: var(--md-sys-color-on-surface); }
.m3-qempty span { font-size: 13px; }

/* -- the composer ---------------------------------------------------------- */
.m3-composer {
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  background-color: var(--md-sys-color-surface-container-lowest);
  overflow: hidden;
}
.m3-composer__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 16px 20px;
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
  background-color: var(--md-sys-color-surface-container-low);
}
.m3-composer__head h4 {
  margin: 0;
  font-size: 16px;
  line-height: 24px;
  font-weight: 600;
  color: var(--md-sys-color-on-surface);
}
.m3-composer__total {
  font-size: 13px;
  color: var(--md-sys-color-on-surface-variant);
  font-variant-numeric: tabular-nums;
}
.m3-composer__body { padding: 20px; }
.m3-composer__row { display: flex; flex-wrap: wrap; gap: 16px; }
.m3-composer__row > * { flex: 1 1 200px; min-width: 0; }
.m3-composer__row > .m3-composer__marks { flex: 0 0 120px; }
.m3-composer__foot {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 16px 20px;
  border-top: 1px solid var(--md-sys-color-outline-variant);
  background-color: var(--md-sys-color-surface-container-low);
}
.m3-composer__why { font-size: 13px; color: var(--md-sys-color-on-surface-variant); }
.m3-composer__note {
  margin: 0 0 16px;
  font-size: 13px;
  line-height: 18px;
  color: var(--md-sys-color-on-surface-variant);
}

/* The bank sits beside "Add a question" rather than under it: writing one and
   taking one are the same job done two ways, and burying the second reads as
   the bank being somewhere else entirely — which is how it stayed unused. */
.m3-composer__bank { margin-left: auto; }
.m3-composer__head .m3-composer__total { margin-left: 12px; }

/* -- the moderator's review of a paper --------------------------------------- */
/* What the paper IS, before what it says: a moderator's first questions are
   who sits it, how long they get, and whether the marks make sense against
   that. A definition list rather than a table — these are five facts about one
   thing, not five rows of the same kind. */
.m3-review {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px 24px;
  margin: 0 0 20px;
  padding: 16px 20px;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  background-color: var(--md-sys-color-surface-container-low);
}
.m3-review > div { min-width: 0; }
.m3-review dt {
  margin: 0 0 2px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .5px;
  text-transform: uppercase;
  color: var(--md-sys-color-on-surface-variant);
}
.m3-review dd {
  margin: 0;
  font-size: 14px;
  line-height: 20px;
  color: var(--md-sys-color-on-surface);
  overflow-wrap: anywhere;
}

/* The verdict sits under the thing being judged, so it is reached by finishing
   the reading rather than by remembering to go back to a list. */
.m3-review__verdict {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 24px;
  padding: 16px 20px;
  border-top: 1px solid var(--md-sys-color-outline-variant);
  background-color: var(--md-sys-color-surface-container-low);
  border-radius: 0 0 var(--md-sys-shape-corner-medium) var(--md-sys-shape-corner-medium);
  font-size: 13px;
}
.m3-review__buttons { display: flex; gap: 8px; flex: 0 0 auto; }

/* -- the picture library ----------------------------------------------------- */
/* Cards, not a table: the thing being chosen is the picture, and a filename in
   a row says nothing about what is in it. */
.m3-piclist {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 16px;
  margin: 16px 0 0;
  padding: 0;
  list-style: none;
}
.m3-piccard {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  background-color: var(--md-sys-color-surface-container-lowest);
  overflow: hidden;
}
.m3-piccard__shot {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 140px;
  background-color: var(--md-sys-color-surface-container-high);
  color: var(--md-sys-color-on-surface-variant);
  font-size: 28px;
}
.m3-piccard__shot img { max-width: 100%; max-height: 100%; object-fit: contain; }
.m3-piccard__body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 12px 14px;
  font-size: 13px;
  min-width: 0;
}
.m3-piccard__body strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The reference is meant to be selected and copied by hand when the clipboard
   call is refused, so it is a field rather than a line of prose. */
.m3-piccard__ref {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
}
.m3-piccard__actions { display: flex; gap: 6px; flex-wrap: wrap; }

/* -- writing a question ------------------------------------------------------ */
/* The toolbar is exactly the allowlist the server keeps, so nothing offered
   here can be silently taken away on save. */
.m3-rte {
  border-radius: var(--md-sys-shape-corner-extra-small) var(--md-sys-shape-corner-extra-small) 0 0;
  background-color: var(--md-sys-color-surface-container-highest);
  border-bottom: 1px solid var(--md-sys-color-on-surface-variant);
  transition: border-color var(--md-sys-motion-short);
}
.m3-rte:focus-within { border-bottom: 2px solid var(--md-sys-color-primary); }
.m3-rte.is-disabled { opacity: .5; }

.m3-rte__bar {
  display: flex;
  gap: 2px;
  padding: 4px 6px;
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
}
.m3-rte__bar button {
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: var(--md-sys-shape-corner-small);
  background: none;
  color: var(--md-sys-color-on-surface-variant);
  font-size: 14px;
  cursor: pointer;
  transition: background-color var(--md-sys-motion-short);
}
.m3-rte__bar button:hover:not([disabled]) { background-color: rgba(0,0,102,.08); }
.m3-rte__bar button:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: -2px;
}
.m3-rte__bar button[disabled] { opacity: .38; cursor: not-allowed; }

.m3-rte__box {
  min-height: 96px;
  padding: 12px 16px;
  font-size: 16px;
  line-height: 24px;
  color: var(--md-sys-color-on-surface);
  overflow-wrap: anywhere;
  overflow-y: auto;
  max-height: 40vh;
}
.m3-rte__box:focus { outline: none; }
/* The placeholder belongs to an empty box only, and data-empty is set from the
   sanitised value — a box holding nothing but a stray <br> is empty. */
.m3-rte__box[data-empty="true"]::before {
  content: attr(data-placeholder);
  color: var(--md-sys-color-on-surface-variant);
  opacity: .7;
  pointer-events: none;
}

/* An answer option is a line, not a page. */
.m3-rte--compact .m3-rte__box { min-height: 24px; padding: 6px 10px; font-size: 15px; }
.m3-rte--compact .m3-rte__bar { padding: 2px 4px; }
.m3-rte--compact .m3-rte__bar button { width: 28px; height: 28px; font-size: 13px; }

/* The link row (V68, prose mode): an inline field rather than window.prompt,
   which blocks the page, cannot be styled and is invisible to a test. */
.m3-rte__link {
  display: flex;
  gap: 6px;
  align-items: center;
  padding: 8px 12px;
  border-top: 1px solid var(--md-sys-color-outline-variant, #ddd);
}

.m3-rte__link input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 30px;
  padding: 4px 8px;
  font-size: 13px;
  border: 1px solid var(--md-sys-color-outline-variant, #ccc);
  border-radius: 6px;
}

.m3-rte__problem {
  margin: 0;
  padding: 6px 12px;
  font-size: 13px;
  color: var(--md-sys-color-on-error-container);
  background-color: var(--md-sys-color-error-container);
}

/* What is being written is what will be read: the editor's own content uses
   the same rules the rendered question does. */
.m3-rte__box img { display: block; max-width: 100%; max-height: 220px; margin: 8px 0; }
.m3-rte__box code {
  padding: 1px 5px;
  border-radius: var(--md-sys-shape-corner-extra-small);
  background-color: var(--md-sys-color-surface-container-high);
  font-size: 90%;
}
.m3-rte__box ul, .m3-rte__box ol { margin: 8px 0; padding-left: 24px; }
/* The legacy `style-main.css` carries a bare `a { display: table }`, which makes
   every link a block box — so a link typed into a lesson broke its own line in
   the editor while rendering inline in the preview beside it. An author cannot
   trust a preview that disagrees with the box they are typing in. */
.m3-rte__box a { display: inline; color: #000066; text-decoration: underline; }
/* Prose mode (V68): headings an author can actually see the shape of. */
.m3-rte__box h2 { font-size: 20px; font-weight: 600; margin: 14px 0 8px; }
.m3-rte__box h3 { font-size: 16.5px; font-weight: 600; margin: 12px 0 6px; }
.m3-rte__box blockquote {
  margin: 12px 0;
  padding: 2px 0 2px 14px;
  border-left: 3px solid #006600;
}

/* -- a question's own formatting -------------------------------------------- */
/* Rendered html inside a prompt, an option or an answer. The rules here are
   about not letting a picture take over the screen it is on: a diagram is part
   of the question on a paper, and a thumbnail in a picker row. */
.m3-qhtml { display: inline; }
.m3-qhtml p:first-child { margin-top: 0; }
.m3-qhtml p:last-child { margin-bottom: 0; }
.m3-qhtml img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 8px 0;
  border-radius: var(--md-sys-shape-corner-small);
}
.m3-qhtml code {
  padding: 1px 5px;
  border-radius: var(--md-sys-shape-corner-extra-small);
  background-color: var(--md-sys-color-surface-container-high);
  color: var(--md-sys-color-on-surface);
  font-size: 90%;
}
.m3-qhtml pre {
  margin: 8px 0;
  padding: 10px 12px;
  border-radius: var(--md-sys-shape-corner-small);
  background-color: var(--md-sys-color-surface-container-high);
  overflow-x: auto;
}
.m3-qhtml pre code { padding: 0; background: none; }
.m3-qhtml ul, .m3-qhtml ol { margin: 8px 0; padding-left: 24px; }

/* In a list of questions the picture is a thumbnail: the row is for choosing,
   and a full-width diagram per row is a gallery nobody asked for. */
.m3-pickrow .m3-qhtml img,
.m3-qcard .m3-qhtml img { max-height: 120px; width: auto; }

/* -- one banked question, read before it is passed -------------------------- */
.m3-bankread__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}
.m3-bankread__prompt {
  margin: 0 0 16px;
  font-size: 16px;
  line-height: 24px;
  color: var(--md-sys-color-on-surface);
  overflow-wrap: anywhere;
}
.m3-bankread__options { margin: 0; padding: 0; list-style: none; }
.m3-bankread__options li {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 10px 14px;
  margin-bottom: 8px;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-small);
  background-color: var(--md-sys-color-surface-container-lowest);
}
/* The memorandum, marked by more than colour: the tick carries it for anyone
   who cannot tell this green from this grey. */
.m3-bankread__options li.is-correct {
  border-color: var(--nl-on-success-container);
  background-color: var(--nl-success-container);
  color: var(--nl-on-success-container);
  font-weight: 500;
}
.m3-bankread__key {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: var(--md-sys-color-surface-container-high);
  color: var(--md-sys-color-on-surface-variant);
  font-size: 12px;
  font-weight: 600;
}
.m3-bankread__options li.is-correct .m3-bankread__key {
  background-color: var(--nl-on-success-container);
  color: #fff;
}

/* -- the bank list's moderation cell ---------------------------------------- */
/* State and the one transition that changes it, together. "Send for
   moderation" as a third button in the Action column was long enough to break
   that column onto three lines per row, and sat beside Edit and Retire, which
   have nothing to do with it. */
.m3-bankstate {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}
.m3-bankstate__send {
  padding: 0;
  border: 0;
  background: none;
  font-size: 12px;
  color: var(--md-sys-color-primary);
  cursor: pointer;
}
.m3-bankstate__send:hover:not([disabled]) { text-decoration: underline; }
.m3-bankstate__send[disabled] { opacity: .5; cursor: not-allowed; }
.m3-bankstate__send:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: 2px;
}

/* Both cells keep their contents on one line each. Without this the list read
   as four buttons and a chip stacked five deep per row: "Send for moderation"
   broke in half, and Edit and Retire fell onto separate lines. */
#root .m3-bankcell-state { white-space: nowrap; width: 1%; }
#root .m3-bankcell-actions { white-space: nowrap; width: 1%; text-align: right; }
#root .m3-bankcell-actions .btn { margin-bottom: 0; }

/* -- the bank picker ------------------------------------------------------- */
/* A VIEW inside the Questions dialog, not a dialog on top of one. The first
   version stacked modals: two sheets of white a hair apart, two ✕ buttons in
   the same corner, and no way to tell which closed what. */
.m3-bankpick__head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  margin-bottom: 20px;
}
.m3-bankpick__head h4 {
  margin: 0;
  font-size: 18px;
  font-weight: 500;
  color: var(--md-sys-color-on-surface);
}
.m3-bankpick__back {
  padding: 0;
  border: 0;
  background: none;
  font-size: 13px;
  color: var(--md-sys-color-primary);
  cursor: pointer;
}
.m3-bankpick__back:hover { text-decoration: underline; }
.m3-bankpick__back:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: 2px;
}

/* Three columns, labels on ONE line and controls on ONE baseline.
   This was a bottom-aligned flex row with the subject's help text inside its
   own column — so that column stood 60px taller than the other two and pulled
   its label and control up by exactly that much. Measured, not guessed: the
   subject label sat at y=191 while Search and Type sat at y=251. The caption
   now spans the row, where it cannot push anything. */
.m3-bankpick__filters {
  display: grid;
  grid-template-columns: minmax(200px, 1.1fr) minmax(260px, 1.8fr) minmax(150px, 0.7fr);
  gap: 6px 16px;
  align-items: start;
  margin-bottom: 20px;
}
/* Every field is label-over-control with the same metrics, so the grid rows
   line up without anyone nudging a margin. */
.m3-bankpick__field { min-width: 0; }
.m3-bankpick__field > label,
.m3-bankpick__field .form-group > label {
  display: block;
  margin: 0 0 6px;
  font-size: 12px;
  font-weight: 500;
  line-height: 16px;
  letter-spacing: .4px;
  color: var(--md-sys-color-on-surface-variant);
}
/* LookupSelect wraps its control in a .form-group whose bottom margin would
   otherwise push the caption off the grid's rhythm. */
.m3-bankpick__field .form-group { margin-bottom: 0; }
.m3-bankpick__field .help-block { display: none; }

.m3-bankpick__hint {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 12px;
  line-height: 16px;
  color: var(--md-sys-color-on-surface-variant);
}

/* The search box is ONE control: a filled field with a leading icon and the
   button inside it. Attached-but-separate — a bordered button glued to a
   borderless filled input — read as two controls, one of them broken. */
.m3-bankpick__search {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 56px;
  padding: 0 8px 0 16px;
  border-radius: var(--md-sys-shape-corner-extra-small) var(--md-sys-shape-corner-extra-small) 0 0;
  background-color: var(--md-sys-color-surface-container-highest);
  border-bottom: 1px solid var(--md-sys-color-on-surface-variant);
  transition: border-color var(--md-sys-motion-short);
}
.m3-bankpick__search:focus-within {
  border-bottom: 2px solid var(--md-sys-color-primary);
}
.m3-bankpick__search > i {
  flex: 0 0 auto;
  font-size: 16px;
  color: var(--md-sys-color-on-surface-variant);
}
.m3-bankpick__search input {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  font-size: 16px;
  color: var(--md-sys-color-on-surface);
}
.m3-bankpick__search input:focus { outline: none; }
/* Safari draws its own clear button on type=search and it sits on top. */
.m3-bankpick__search input::-webkit-search-decoration,
.m3-bankpick__search input::-webkit-search-results-button { display: none; }
.m3-bankpick__search button {
  flex: 0 0 auto;
  height: 40px;
  padding: 0 16px;
  border: 0;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: filter var(--md-sys-motion-short);
}
.m3-bankpick__search button:hover { filter: brightness(1.15); }
.m3-bankpick__search button:focus-visible {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: 2px;
}

/* A fixed area for the results so the dialog does not jump between a page of
   ten and an empty state, and so the foot stays where the eye left it. */
.m3-bankpick__results {
  min-height: 220px;
  max-height: 46vh;
  overflow-y: auto;
  margin-bottom: 16px;
}

.m3-picklist { margin: 0; padding: 0; list-style: none; }

/* A ticked list, not a table. The prompt is prose and must wrap, and the row
   is the control, so the whole thing is one <label> — clicking anywhere on a
   question ticks it, which is what every picker people already use does. */
.m3-pickrow {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  width: 100%;
  margin-bottom: 8px;
  padding: 12px 14px;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-medium);
  background-color: var(--md-sys-color-surface-container-lowest);
  font-weight: 400;
  cursor: pointer;
  transition: border-color var(--md-sys-motion-short),
              background-color var(--md-sys-motion-short);
}
.m3-pickrow:hover { border-color: var(--md-sys-color-outline); }
.m3-pickrow--on {
  border-color: var(--md-sys-color-primary);
  background-color: var(--md-sys-color-primary-container);
}
/* Already copied onto this paper: still readable, but not offered again. */
.m3-pickrow--had { opacity: .55; cursor: not-allowed; }
.m3-pickrow input[type="checkbox"] {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin: 3px 0 0;
  cursor: inherit;
}
.m3-pickrow:focus-within {
  outline: 2px solid var(--md-sys-color-primary);
  outline-offset: 2px;
}
.m3-pickrow__body { flex: 1 1 auto; min-width: 0; }
.m3-pickrow__body .m3-qcard__prompt { display: block; margin: 0 0 8px; }

/* The palette's own success and warning containers, the same pair
   .label-success and .label-warning use — a chip inventing its own greens is
   how two greens end up on one screen. */
.m3-qchip--ok {
  background-color: var(--nl-success-container);
  color: var(--nl-on-success-container);
}
.m3-qchip--warn {
  background-color: var(--nl-warning-container);
  color: var(--nl-on-warning-container);
}
.m3-qchip--had {
  background-color: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
}

/* No dashed box around "nothing matched": a dashed outline reads as a drop
   zone waiting for a file, not as an answer to a search. */
.m3-qempty--plain {
  border: 0;
  background: none;
}

.m3-bankpick__paging {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 0;
  border-top: 1px solid var(--md-sys-color-outline-variant);
  font-size: 13px;
}
.m3-bankpick__pagebtns { display: flex; gap: 8px; }

/* The picker's own foot, because the dialog's belongs to the paper behind it.
   Same rhythm as the composer's: a tinted band, content left, action right. */
.m3-bankpick__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin: 0 -15px -15px;
  padding: 16px 20px;
  border-top: 1px solid var(--md-sys-color-outline-variant);
  background-color: var(--md-sys-color-surface-container-low);
}
.m3-bankpick__tally { font-size: 13px; }

/* The tint carries the caution, not the text colour: on-warning-container is
   black, which says nothing on the foot's own background, and the brand
   orange on white is nowhere near 4.5:1. */
.m3-pick__warn {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--md-sys-shape-corner-small);
  background-color: var(--nl-warning-container);
  color: var(--nl-on-warning-container);
}

@media (max-width: 767px) {
  .m3-bankpick__filters { grid-template-columns: 1fr; }
  .m3-bankpick__results { max-height: none; }
  .m3-bankpick__foot { margin: 0 -10px -10px; padding: 12px 14px; }
}

/* -- answer options -------------------------------------------------------- */
/* Its own row rather than a Bootstrap input-group. The group model put a border
   round every part, so a two-addon row read as four separate controls with a
   tall bordered box on the end; here the row IS the control. */
.m3-option {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 48px;
  padding: 0 8px 0 12px;
  margin-bottom: 8px;
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--md-sys-shape-corner-small);
  background-color: var(--md-sys-color-surface-container-lowest);
  transition: border-color var(--md-sys-motion-short),
              background-color var(--md-sys-motion-short);
}
.m3-option:focus-within { border-color: var(--md-sys-color-primary); }
/* The correct answer is the one fact a marker has to see at a glance. */
.m3-option.is-correct {
  border-color: var(--nl-success);
  background-color: var(--nl-success-container);
}

.m3-option__pick { flex: 0 0 auto; margin: 0; width: 18px; height: 18px; cursor: pointer; }
.m3-option__pick:focus-visible { outline: 2px solid var(--md-sys-color-primary); outline-offset: 2px; }

.m3-option__key {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--md-sys-shape-corner-full);
  background-color: var(--md-sys-color-surface-container-high);
  color: var(--md-sys-color-on-surface-variant);
  font-size: 13px;
  font-weight: 600;
}
.m3-option.is-correct .m3-option__key {
  background-color: var(--nl-success);
  color: #FFFFFF;
}

/* Borderless: the row already carries the border, and a field inside a field
   is the doubled-outline look this screen had everywhere. */
.m3-option__text {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--md-sys-color-on-surface);
  font-family: inherit;
  font-size: 14px;
  line-height: 20px;
}
.m3-option__text:focus { outline: none; }

/* The editor inside an answer row is the row's field, not a chip sitting in
   it: the row already carries the border and the fill, so the editor drops
   both and takes the width the old <input> had. */
.m3-option .m3-rte {
  flex: 1 1 auto;
  min-width: 0;
  background: none;
  border-bottom: 0;
}
.m3-option .m3-rte__box {
  min-height: 20px;
  padding: 0;
  font-size: 14px;
  line-height: 20px;
}
/* The toolbar appears on focus, so the row grows by its height rather than
   overlapping what is under it.
 
   Keyed on the TOOLBAR being present, not on `:focus-within`, and the
   difference is not cosmetic. `:focus-within` is also true while the row's own
   radio is being pressed — a mousedown focuses it — so pressing the radio grew
   its own row by 14px and slid the radio down out from under the pointer
   before mouseup. Chrome then delivered no click at all: measured on
   2026-09-24, the radio's box moved from y=439.5 to y=453.5 mid-gesture and
   `elementFromPoint` at mouseup answered the row, not the radio. The author
   clicked "this is the correct answer", nothing happened, and Add Question
   stayed greyed saying "Mark which answer is correct" — which is what writing
   a quiz felt like.
 
   The toolbar is rendered only while the editor itself has focus, so it is the
   exact signal this rule wanted in the first place. */
.m3-option:has(.m3-rte__bar) { height: auto; min-height: 48px; padding-top: 4px; padding-bottom: 4px; }
.m3-option .m3-rte__bar {
  padding: 0 0 4px;
  border-bottom: 0;
}

/* True/False writes its own two answers, so the row shows them as TEXT rather
   than as a field. A disabled input would say "you may not type here"; there
   was never anything to type. */
.m3-option__fixed {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 14px;
  line-height: 20px;
  font-weight: 500;
  color: var(--md-sys-color-on-surface);
}
.m3-option__text::placeholder { color: var(--md-sys-color-on-surface-variant); opacity: .7; }
.m3-option__text[disabled] { color: rgba(0,0,0,.38); }

.m3-option__hint {
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--md-sys-color-on-surface-variant);
}

@media (max-width: 767px) {
  .m3-qcard { gap: 12px; padding: 12px; }
  .m3-qcard__actions { opacity: 1; }
  .m3-composer__body, .m3-composer__head, .m3-composer__foot { padding: 12px 14px; }
  .m3-option { gap: 8px; padding: 0 4px 0 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .m3-qcard, .m3-qcard__actions, .m3-icon-btn, .m3-option { transition: none; }
}

/* The student-view preview header: what the paper is, and what it allows. */
.m3-preview__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 0 0 16px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
}

/* ---------------------------------------------------------------- exam reuse
   Putting a moderated paper onto other classes. A checklist rather than a
   multi-select: the targets are few, and a list you can read at a glance beats
   a control you have to open. */
.m3-reuse__classes {
    max-height: 240px;
    overflow-y: auto;
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: 10px;
    padding: 6px 12px;
}
.m3-reuse__class {
    display: block;
    font-weight: 400;
    padding: 5px 0;
    margin: 0;
    cursor: pointer;
}
.m3-reuse__class:hover {
    color: var(--md-sys-color-primary);
}
.m3-reuse__title {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 8px;
}
/* Fixed width so a column of class codes lines up and the inputs do too —
   ragged left edges on a stack of near-identical rows are hard to scan. */
.m3-reuse__title-class {
    flex: 0 0 190px;
    font-size: 13px;
    color: var(--md-sys-color-on-surface-variant);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.m3-reuse__report {
    list-style: none;
    padding: 0;
    margin: 12px 0 0;
}
.m3-reuse__report li {
    padding: 7px 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
}
.m3-reuse__report li:last-child { border-bottom: 0; }
.m3-reuse__report .fa-check { color: var(--md-sys-color-tertiary, #006600); }
.m3-reuse__report .fa-times { color: var(--md-sys-color-error, #e60000); }
