/* AIMPHAN admin web UI — the persistent shell (#49). */

/*
 * Its own stylesheet, enqueued only on ?admin= routes (inc/assets.php).
 *
 * None of this is reachable from the public library, and the gate and the
 * catalog are the most-hit pages on the site — shipping an admin rail's CSS to
 * every reader would be a straight regression for zero readers' benefit.
 *
 * Tokens (--brand, --line, --ink-muted, --fs-*, --shadow-card …) come from
 * theme.css, which this file declares as a dependency. No raw hex here.
 */

/*
 * The same layer order theme.css declares (#63).
 *
 * Restated rather than assumed: order is set by the first @layer statement the
 * document sees and theme.css always gets there first, but a sheet that says
 * `@layer components` without saying what that is cannot be read on its own.
 * Repeating it is a no-op.
 *
 * Everything here is components except the shell and the rail, which are the
 * admin's page skeleton — that is what lets `.adminshell .adminui` take the
 * padding back off `.adminui` without a specificity race.
 */
@layer wp, tokens, base, layout, components, utilities;

@layer layout {
  /*
   * The shell: navigation rail beside content.
   *
   * Deliberately the same flex shape as the catalog's .catalog__body — rail with
   * a fixed width, content taking the rest with min-width: 0 so a wide table
   * scrolls inside its own box instead of pushing the rail off screen. Staff move
   * between the two surfaces all day; they should not have to relearn the layout.
   */
  .adminshell {
    display: flex;
    gap: 32px;
    align-items: flex-start;
    padding: 24px 20px 40px;
  }

  .adminshell__main {
    flex: 1;
    min-width: 0;
  }

  /* .adminui carries the page padding when it is the whole page. Inside the
     shell the shell owns it, so the two must not add up. */
  .adminshell .adminui {
    padding: 0;
  }
}

@layer components {
  /* The hub's "signed in as …" line, which is a lede now that it is no longer
     inside a centred gate card. */
  .adminui__lede {
    margin: 0 0 20px;
    color: var(--ink-muted);
  }
}

@layer layout {
  /* ---- The rail ---------------------------------------------------------- */

  .adminnav {
    width: 256px;
    flex-shrink: 0;
  }

  .adminnav__title {
    margin: 0 0 12px;
    padding: 0 4px;
    font-size: var(--fs-small);
    font-weight: 700;
    color: var(--ink-muted);
  }

  .adminnav__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 4px;
  }

  .adminnav__item {
    /*
     * 44x44 floor, stated rather than implied. At the Foundation's 15px body step
     * a 8px+8px padding box lands at ~39px — the same near-miss .pill and .button
     * were both found with.
     */
    min-height: 44px;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    /*
     * The left rail is where state lives, and it is shape before colour:
     * transparent = ours and not current, dashed = leaves for WordPress,
     * solid = the area on screen. Someone who cannot separate blue from grey
     * still sees a line that is there, dashed, or absent.
     */
    border-left: 3px solid transparent;
    border-radius: 10px;
    color: var(--ink);
    transition: background 0.15s ease;
  }

  .adminnav__item:hover {
    background: var(--card);
  }

  .adminnav__text {
    flex: 1;
    min-width: 0;
    display: grid;
    gap: 1px;
  }

  .adminnav__label {
    font-weight: 600;
  }

  /*
   * The "still opens WordPress" hint. Same wording as the hub tiles'
   * .admin-hub__item-wp, and deliberately the same faint treatment: it is
   * information about where the link goes, not a warning.
   */
  .adminnav__wp {
    font-size: var(--fs-tiny);
    /* Was --ink-faint at 2.38:1 — the worst contrast in the theme (#79). */
    color: var(--ink-muted);
  }

  .adminnav__item--wp {
    border-left-style: dashed;
    border-left-color: var(--line);
  }

  /*
   * Current area.
   *
   * aria-current="page" in the markup is what a screen reader reads; this is the
   * matching visual, and it is three cues that are not colour — a solid rail, a
   * heavier label, a raised card — plus one that is.
   */
  .adminnav__item.is-active {
    background: var(--card);
    border-left-color: var(--brand);
    box-shadow: var(--shadow-card);
  }

  .adminnav__item.is-active .adminnav__label {
    font-weight: 800;
    color: var(--brand-ink);
  }

  .adminnav__dot {
    flex: 0 0 auto;
    font-size: var(--fs-tiny);
    color: var(--brand);
  }

  /* ---- Rail footer ------------------------------------------------------- */

  .adminnav__foot {
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
    display: grid;
    gap: 4px;
  }

  .adminnav__foot form {
    margin: 0;
  }

  .adminnav__link {
    min-height: 44px;
    width: 100%;
    display: flex;
    align-items: center;
    padding: 8px 14px;
    border-radius: 10px;
    font-size: var(--fs-small);
    font-weight: 600;
    color: var(--ink-muted);
    transition: background 0.15s ease, color 0.15s ease;
  }

  .adminnav__link:hover {
    background: var(--card);
    color: var(--brand);
  }

  /* The sign-out control is a <button> because it submits a nonced form; these
     are the declarations a button does not inherit from the anchor above. */
  .adminnav__signout {
    border: 0;
    background: transparent;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
  }

  /* ---- Tablet: 768–1023 -------------------------------------------------- */

  /*
   * 768, not 767 + 1: the non-overlapping complement of the phone query below,
   * and where the Foundation's tablet band begins.
   *
   * Sticky only from here up. The codes table is long and this is a screen staff
   * sit in all day, so the rail should not scroll away — but on a phone the rail
   * is a horizontal scroller in normal flow, where sticky would pin a strip over
   * the content instead.
   */
  @media (min-width: 768px) {
    .adminnav {
      position: sticky;
      top: 20px;
    }
  }

  /* Below the desktop band the rail gives width back to the content, which is
     where the tables are. */
  @media (max-width: 1023px) {
    .adminshell {
      gap: 20px;
    }

    .adminnav {
      width: 208px;
    }
  }

  /* ---- Phone: ≤767 ------------------------------------------------------- */

  @media (max-width: 767px) {
    .adminshell {
      flex-direction: column;
      gap: 16px;
      padding: 16px 16px 32px;
      /*
       * Back to stretch, and not optional.
       *
       * flex-start on a column stack sizes children to their own content in the
       * cross axis, so .adminshell__main grew to the max-content width of the
       * codes table — 579px inside a 371px viewport — and took the whole document
       * sideways with it. min-width: 0 does not help: it is a floor, not a
       * ceiling. Stretch makes the column exactly as wide as the shell, which is
       * what .adminui__scroll needs in order to be the thing that scrolls.
       */
      align-items: stretch;
    }

    .adminnav {
      width: 100%;
    }

    /*
     * The same answer the catalog's category rail already gives: a horizontal
     * scroller, not a drawer. Nothing to open, no focus to trap, no state to get
     * wrong, and the current area stays visible instead of being hidden behind a
     * button that has to explain itself.
     */
    .adminnav__list {
      display: flex;
      flex-wrap: nowrap;
      overflow-x: auto;
      gap: 8px;
      padding-bottom: 6px;
      -webkit-overflow-scrolling: touch;
    }

    .adminnav__list li {
      flex: 0 0 auto;
    }

    .adminnav__item {
      white-space: nowrap;
      /*
       * The state rail moves to the bottom edge. In a row of chips a left border
       * reads as a divider between two of them, not as a mark on one.
       */
      border-left: 0;
      border-bottom: 3px solid transparent;
      border-radius: 10px;
      background: var(--card);
    }

    .adminnav__item--wp {
      border-bottom-style: dashed;
      border-bottom-color: var(--line);
    }

    .adminnav__item.is-active {
      border-bottom-color: var(--brand);
    }

    .adminnav__foot {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
      margin-top: 12px;
    }

    .adminnav__link {
      width: auto;
    }
  }
}

@layer components {
  /* ---- Codes screen: head, filters, dialog (#51, #52) -------------------- */

  /*
   * The client's layout rule, stated once so the rest of this section can just
   * point at it: tables go full width, forms and prose are centred at a readable
   * measure. The table below takes the whole panel; the create form is held to
   * ~560px inside the dialog; running text is capped at a line length someone can
   * actually read.
   */

  /* The screen title and the one primary action, on the same line. */
  .adminui__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
  }

  /*
   * .btn-brand is width: 100% because the gate's forms stack it full width. Next
   * to a heading it is one control among several and has to size to its label.
   */
  .adminui__new {
    width: auto;
    min-height: 44px;
  }

  /* Nothing narrows a table: it is data, and it wants every pixel it can get. */
  .adminui__panel--table {
    width: 100%;
  }

  /*
   * Make the table's scroll box a containing block.
   *
   * .adminui__table's last <th> holds a .screen-reader-text label, which is
   * position: absolute. With no positioned ancestor its containing block was the
   * page, so it resolved at the far end of the 553px table, outside the scroller
   * that was supposed to clip it — and dragged the whole document 160px sideways
   * on a 375px phone. overflow only clips descendants it is the containing block
   * for; this is what makes it one.
   */
  .adminui__scroll {
    position: relative;
  }

  /*
   * The filter bar spans the table it filters rather than being centred at a
   * form's measure — it belongs to the table, and a narrow bar floating over a
   * full-width table reads as two unrelated things. The readable-measure rule is
   * applied per control instead, so no single input stretches to 1300px.
   */
  .adminui__filters {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
    margin: 0 0 16px;
    padding-bottom: 16px;
    border-bottom: 1px solid var(--line);
  }

  .adminui__filter {
    flex: 1 1 200px;
    /* min-width: 0 so a long school name cannot push the bar wider than the
       panel; max-width so a wide desktop does not stretch one input across it. */
    min-width: 0;
    max-width: 320px;
    display: grid;
    gap: 6px;
  }

  /* A four-digit year needs four digits of room, not a third of the bar. */
  .adminui__filter--year {
    flex: 0 1 148px;
  }

  /* 44px floor, stated. .field's 12px+12px padding round a 24.8px line box is the
     same ~39px near-miss the rail's items were found with. */
  .adminui__filter .field {
    min-height: 44px;
  }

  .adminui__filter-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
  }

  .adminui__filter-actions .btn-brand {
    width: auto;
    min-height: 44px;
  }

  .adminui__clear {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 12px;
    border-radius: 10px;
    font-size: var(--fs-small);
    font-weight: 600;
    color: var(--ink-muted);
    transition: background 0.15s ease, color 0.15s ease;
  }

  .adminui__clear:hover {
    background: var(--sunken);
    color: var(--brand);
  }

  /* Running text — the row range, the empty state — at a readable measure. */
  .adminui__hint {
    margin: 0 0 12px;
    max-width: 68ch;
    font-size: var(--fs-small);
    color: var(--ink-muted);
  }

  /* ---- The create dialog ------------------------------------------------- */

  /*
   * One element, two states.
   *
   * Server-rendered `open` — the no-JS path, and a submission that came back
   * rejected — has to sit in the normal flow above the table; the UA stylesheet's
   * absolute position would float it over rows it has nothing to do with.
   * admin.js adds .is-modal when it re-opens the same element with showModal(),
   * which is the only state where fixed-and-centred is right.
   *
   * .is-modal is paired with [open] deliberately: without that, its display would
   * out-specify the UA's `dialog:not([open]) { display: none }` and a closed
   * dialog would stay on screen if the class were ever left behind.
   */
  .admindlg {
    position: static;
    width: 100%;
    max-width: none;
    margin: 0 0 16px;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--ink);
  }

  .admindlg.is-modal[open] {
    position: fixed;
    inset: 0;
    width: 100%;
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    margin: 0;
    padding: 16px;
    display: flex;
    /* flex-start + auto margins on the panel, not align-items: center — centring
       clips the top of a panel taller than the viewport instead of scrolling it. */
    align-items: flex-start;
    justify-content: center;
    overflow: auto;
    overscroll-behavior: contain;
  }

  /*
   * color-mix rather than a second copy of --ink's value: the dim is that token
   * at 55%, and writing the hex again is how the two drift apart.
   */
  .admindlg::backdrop {
    background: color-mix(in srgb, var(--ink) 55%, transparent);
  }

  /* The other half of the layout rule: a form gets a readable measure, centred. */
  .admindlg__panel {
    width: min(560px, 100%);
    margin: 0 auto;
    padding: 20px;
    border-radius: var(--radius-card);
    background: var(--card);
    box-shadow: var(--shadow-pop);
  }

  .admindlg.is-modal[open] .admindlg__panel {
    margin: auto;
  }

  .admindlg__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
  }

  .admindlg__close {
    flex: 0 0 auto;
    display: inline-grid;
    place-items: center;
    width: 44px;
    height: 44px;
    /* Pulled into the panel's own padding so the glyph optically aligns with the
       panel edge while the 44x44 hit area keeps its full size. */
    margin: -6px -8px 0 0;
    border-radius: 10px;
    font-size: var(--fs-heading);
    line-height: 1;
    color: var(--ink-muted);
    transition: background 0.15s ease, color 0.15s ease;
  }

  .admindlg__close:hover {
    background: var(--sunken);
    color: var(--ink);
  }

  /* One column, not .adminui__form's four across: four fields inside a 560px
     dialog would get about 120px each. */
  .admindlg__form {
    display: grid;
    gap: 14px;
  }

  .admindlg__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
  }

  .admindlg__actions .btn-brand {
    width: auto;
    min-height: 44px;
  }

  .admindlg__cancel {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 16px;
    border-radius: 999px;
    font-weight: 600;
    color: var(--ink-muted);
    transition: background 0.15s ease, color 0.15s ease;
  }

  .admindlg__cancel:hover {
    background: var(--sunken);
    color: var(--ink);
  }

  /* ---- The destructive confirmation (#58) -------------------------------- */

  /*
   * The dialog's prose. It is the only text on either screen a reader must not
   * skim, so it sits at a readable measure and at body size rather than the
   * --fs-small the rest of this admin's supporting text uses.
   */
  .admindlg__warn {
    margin: 0 0 12px;
    max-width: 52ch;
    line-height: var(--lh-body);
    color: var(--ink);
  }

  /*
   * The line that says what is about to be destroyed and how much of it.
   *
   * A left rule rather than a colour change on the text: the sentence still has
   * to be read at body weight, and someone who cannot separate red from grey
   * still sees a marked block. --red-ink, so the mark is the same red as the
   * button below it.
   */
  .admindlg__warn--danger {
    margin-bottom: 16px;
    padding-left: 12px;
    border-left: 3px solid var(--red-ink);
    font-weight: 600;
  }

  /*
   * The confirm button, deliberately not .btn-brand.
   *
   * Brand blue is what every safe primary action on this admin looks like, and
   * the one control that deletes a class's results must not be the same shape and
   * colour as "create a code" — muscle memory is exactly what a confirmation
   * dialog exists to interrupt.
   *
   * --red at full strength on white measures about 3.7:1, which fails AA for a
   * 15px label. This file used to answer that on its own, with a 60/40 mix into
   * --ink that no other red in the theme shared. --red-ink is that same idea
   * promoted to a token (#63), so the exam's blank-answer chips, the field
   * error message and this button are now one red rather than four — white on
   * it measures 6.48:1, and the hover step 7.69:1.
   */
  .admindlg__danger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 0 18px;
    border: 0;
    border-radius: 999px;
    background: var(--red-ink);
    /*
     * --card is this system's white. .btn-brand writes #fff literally, but this
     * file's rule is no raw hex and there is no on-accent ink token to reach for.
     */
    color: var(--card);
    font: inherit;
    font-weight: 700;
    cursor: pointer;
    transition: background 0.15s ease;
  }

  /* One step deeper on the same ramp, rather than a second recipe. */
  .admindlg__danger:hover {
    background: oklch(from var(--red-ink) calc(l * 0.88) c h);
  }

  .admindlg__danger:focus-visible {
    outline: none;
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--red-ink) 25%, transparent);
  }

  /* ---- The room detail screen (#58) -------------------------------------- */

  /* Back to the list, beside the room's name where the codes screen puts its
     one primary action. A link, not a button: it goes somewhere. */
  .adminui__back {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 12px;
    border-radius: 10px;
    font-size: var(--fs-small);
    font-weight: 600;
    color: var(--ink-muted);
    transition: background 0.15s ease, color 0.15s ease;
  }

  .adminui__back:hover {
    background: var(--sunken);
    color: var(--brand);
  }

  /*
   * The room's facts.
   *
   * A definition list, because that is what six label/value pairs are, laid out
   * on auto-fit columns so it reflows from three across to one without a
   * breakpoint of its own. Not a table: it is one record, and a table of one row
   * makes a reader look for the second.
   */
  .adminui__facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 16px;
    margin: 0 0 20px;
  }

  .adminui__fact {
    display: grid;
    gap: 4px;
    min-width: 0;
  }

  .adminui__fact dt {
    font-size: var(--fs-small);
    font-weight: 600;
    color: var(--ink-muted);
  }

  .adminui__fact dd {
    margin: 0;
    /* Room and subject names are teacher-supplied free text with no length limit
       short of the column's 191 — it wraps rather than widening the grid. */
    overflow-wrap: anywhere;
  }

  /*
   * Export and delete on one line, and deliberately far apart in weight: the
   * export is the thing staff come here to do, the delete is the thing they must
   * not do by accident. margin-left: auto pushes the destructive one to the
   * opposite end of the row rather than next to the button beside it.
   */
  .adminui__room-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    padding-top: 16px;
    border-top: 1px solid var(--line);
  }

  .adminui__room-actions form {
    margin: 0;
  }

  .adminui__room-actions .exam__delete {
    margin-left: auto;
  }

  /*
   * The row's link into a room. .adminui__table sets 10px cell padding, which
   * leaves a bare anchor about 35px tall — the same near-miss the rail's items
   * and the filter bar's fields were both found with.
   */
  .adminui__rowlink {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: 0 12px;
    border-radius: 10px;
    font-weight: 600;
    color: var(--brand);
    transition: background 0.15s ease;
  }

  .adminui__rowlink:hover {
    background: var(--brand-light);
  }

  /* ---- Narrow panel: the filter bar stacks ------------------------------- */

  /*
   * One control per row — asked of the panel, not the window (#63).
   *
   * The reason was always a measurement of the BAR: a flex-basis of 200px packs
   * two fields into anything under ~460px at ~175px each, narrower than the
   * school names in them. 767 stood in for that number and was wrong both ways
   * — at 768 the panel is 445px and this now stacks where the old query did
   * not; at 800 it is 517px and the controls sit side by side where the old
   * query stacked them. A 390px phone leaves 318px and still stacks.
   */
  @container adminui-panel (max-width: 460px) {
    .adminui__filter {
      flex: 1 1 100%;
      max-width: none;
    }

    .adminui__filter--year {
      flex: 1 1 100%;
    }

    .adminui__filter-actions,
    .adminui__filter-actions .btn-brand {
      width: 100%;
    }

    .adminui__filter-actions .btn-brand {
      flex: 1 1 100%;
    }
  }

  /* ---- Phone: <=767 ------------------------------------------------------ */

  @media (max-width: 767px) {
    /* .adminui__new sits in the screen head, outside any panel — the window is
       the right thing to ask about it. */
    .adminui__new {
      width: 100%;
    }

    .admindlg.is-modal[open] {
      padding: 12px;
    }

    .admindlg__panel {
      padding: 16px;
    }

    /*
     * Stacked, and in DOM order — which puts the primary action at the bottom,
     * nearest the thumb, and keeps the same cancel-then-confirm reading order the
     * row has on a wider screen.
     */
    .admindlg__actions {
      flex-direction: column;
      align-items: stretch;
    }

    .admindlg__cancel {
      justify-content: center;
    }

    /*
     * The destructive confirm stacks full width with the rest, but it stays the
     * LAST item in DOM order rather than being pulled to the top — the reading
     * order on a phone should be the same cancel-then-confirm it is everywhere
     * else, and reordering the one dialog where the outcome is irreversible is
     * how a thumb lands on the wrong control.
     */
    .admindlg__danger {
      width: 100%;
    }

  }

  /* ---- Narrow panel: the room's facts and its two actions ---------------- */

  /*
   * Room and subject names are long in Thai and the room's own title is already
   * the page heading, so a narrow panel gets one fact per line. 420px is where
   * the auto-fit track's own 180px minimum stops fitting two of them, so the
   * two rules now agree instead of the media query overruling the grid it sits
   * on.
   */
  @container adminui-panel (max-width: 420px) {
    .adminui__facts {
      grid-template-columns: 1fr;
      gap: 12px;
    }
  }

  /*
   * Stacked, and the margin-left: auto that separated the two actions on a wide
   * row has to go with it — on a column it would collapse to nothing and leave
   * delete sitting directly under export, which is the adjacency the desktop
   * layout deliberately avoids. A rule and a gap do the separating instead.
   */
  @container adminui-panel (max-width: 460px) {
    .adminui__room-actions {
      flex-direction: column;
      align-items: stretch;
    }

    .adminui__room-actions .btn-brand {
      width: 100%;
    }

    .adminui__room-actions .exam__delete {
      margin-left: 0;
      margin-top: 4px;
      padding-top: 12px;
      border-top: 1px solid var(--line);
      border-radius: 0;
    }
  }

  /* ---- Entitlements + question bank (#61) -------------------------------- */

  /*
   * The layout rule the codes section states applies here too: tables go full
   * width, forms and prose are held to a readable measure. These two screens are
   * the first where a form sits on the page rather than inside a dialog, so the
   * measure has to be declared on the form itself.
   */
  .adminui__form {
    display: grid;
    gap: 14px;
    max-width: 560px;
  }

  .adminui__textarea {
    min-height: 96px;
    resize: vertical;
  }

  /*
   * The save bar: the control that decides what a save records, next to the
   * button that performs it. Marooning the button at the far end of a long table
   * is how a staff member ends up not knowing which staff name was attached.
   */
  .adminui__savebar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px solid var(--line);
  }

  .adminui__save {
    width: auto;
    min-height: 44px;
  }

  /*
   * The sentence that says a save applies to the rows on screen and no others.
   *
   * Deliberately not .adminui__hint: a hint is optional reading, and this is the
   * rule the button actually follows. --gold is the Foundation's attention
   * colour, used as a left rule so a reader who cannot distinguish it still gets
   * a marked block — the same device .admindlg__warn--danger uses for --red.
   */
  .adminui__note {
    margin: 0 0 14px;
    padding: 10px 14px;
    max-width: 70ch;
    border-left: 3px solid var(--gold);
    border-radius: 0 10px 10px 0;
    background: var(--sunken);
    font-size: var(--fs-small);
    line-height: var(--lh-body);
    color: var(--ink);
  }

  /*
   * A checkbox is a ~13px target by default, and this one decides whether a
   * paying school can open a book. The whole cell becomes the target instead —
   * 44x44 around the box — and the title in the next column is wired to the same
   * input, so the easiest thing to hit is also the thing that names what it does.
   */
  .adminui__tickcol {
    width: 1%;
  }

  .adminui__tick {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-width: 44px;
    min-height: 44px;
    padding: 0 8px;
    border-radius: 10px;
    cursor: pointer;
  }

  .adminui__tick:hover {
    background: var(--sunken);
  }

  .adminui__tick input {
    width: 18px;
    height: 18px;
    accent-color: var(--brand);
  }

  .adminui__ticklabel {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    cursor: pointer;
  }

  /* The CSV import's "replace everything first" switch, on the same 44px floor. */
  .adminui__check {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 44px;
    font-size: var(--fs-small);
    color: var(--ink);
    cursor: pointer;
  }

  .adminui__check input {
    width: 18px;
    height: 18px;
    accent-color: var(--brand);
  }

  /*
   * One row per choice: the radio that marks it correct and the text it contains
   * answer the same question together, so they are read together.
   */
  .adminui__choices {
    display: grid;
    gap: 8px;
    margin: 0;
    padding: 0;
    border: 0;
  }

  .adminui__choice {
    display: flex;
    align-items: center;
    gap: 10px;
  }

  .adminui__choice .field {
    flex: 1 1 auto;
    min-width: 0;
  }

  /* Stored lowercase, shown uppercase — a question's answer key reads as "B"
     everywhere a human sees it, including the table's own pill. */
  .adminui__choice-key {
    font-weight: 800;
    color: var(--ink-muted);
  }

  .adminui__choice-line {
    padding: 2px 0;
  }

  /*
   * A section heading that owns one action, borrowing the screen title's layout.
   * The title's own bottom margin goes, or the row gets it twice.
   */
  .adminui__head--inline {
    margin-bottom: 14px;
  }

  .adminui__head--inline .adminui__panel-title {
    margin-bottom: 0;
  }

  /* ---- Narrow panel: the save bar and the wrapping cell ------------------ */

  /*
   * The save bar stacks. Side by side inside a 318px panel the select is too
   * narrow to read a school-staff name in and the button is too narrow to hit —
   * and that is a fact about the panel, which is what the bar is laid out in.
   */
  @container adminui-panel (max-width: 460px) {
    .adminui__savebar {
      flex-direction: column;
      align-items: stretch;
    }

    .adminui__savebar .adminui__save {
      width: 100%;
    }
  }

}

/*
 * Cells that hold names rather than values — and a rule that never once fired
 * (#63).
 *
 * .adminui__table sets `white-space: nowrap` so a code or a date cannot break
 * mid-value. A title or a question is a sentence, and Thai writes without
 * spaces, so an unwrapped one is one very long token that drags the scroller
 * out to nothing useful. That is what this class is for.
 *
 * It never worked: `.adminui__table td` is a class AND a type selector, 0,1,1
 * against this modifier's 0,1,0. min-width and max-width applied because
 * nothing contested them, so the column looked deliberate while doing the
 * opposite of what it says. With a real 113-character Thai question at 1360 the
 * cell was 312px on one line and the table 1204px inside a 977px scroller; it
 * is now 220px over three lines and the table 1112px, for one row. The dev rows
 * are all short, which is why nobody saw it.
 *
 * The third specificity fight this file has lost, and the reason #63 declared
 * layers. The fix is not a third class or an !important but to say what the
 * class is: a single-purpose override that beats a component default, which is
 * what `utilities` holds. The BEM name reads oddly there; renaming means
 * editing template-parts, which is not this card's to touch.
 */
@layer utilities {
  .adminui__cell--wrap {
    white-space: normal;
    min-width: 220px;
    max-width: 42ch;
  }

  /*
   * A 220px floor on the first column pushes the table into its own scroller as
   * soon as the panel is narrow. The scroller still works, but the column staff
   * read to decide anything is the one that should not need it.
   *
   * 560px, because that is roughly where the other columns stop leaving 220 for
   * this one — the table is inside the panel, so the panel is what to ask.
   */
  @container adminui-panel (max-width: 560px) {
    .adminui__cell--wrap {
      min-width: 160px;
    }
  }
}

@layer components {
}


@layer components {
  /*
   * The boundary between "stays here" and "leaves to WordPress" (#77).
   *
   * A rule and a little air, nothing louder. The wp-admin entries are still
   * ordinary items in the same rail — this only marks where the product ends,
   * so someone sees it before the click rather than after landing in a
   * different chrome with no way back but the browser.
   *
   * On the <li> rather than the <a> so the space belongs to the list, and the
   * item's own hover and active backgrounds keep the size they had.
   *
   * In @layer components like every other rule in this file. Appended
   * unlayered it would have outranked the entire stylesheet — harmless for one
   * border, wrong as a precedent.
   */
  .adminnav__break {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid color-mix(in oklab, var(--line) 70%, transparent);
  }
}
